package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"

	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/sse"
	context "github.com/kataras/iris/v12/context"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

type DebugStage struct {
	DebugEvent `json:",inline"`
	Events     []*DebugEvent `json:"events"`
}

type DebugEvent struct {
	ID      string      `json:"id"`
	Type    string      `json:"type"`
	Status  string      `json:"status"`
	Created int64       `json:"created"`
	Error   *errs.Error `json:"error"`
	Data    interface{} `json:"data"`
}

type Request struct {
	ID      string `json:"id"`
	Status  string `json:"status"`
	Created int64  `json:"created"`
}

type DebugTaks struct {
	ID           string        `json:"id"`
	Status       string        `json:"status"`
	Created      int64         `json:"created"`
	Stages       []*DebugStage `json:"stages"`
	CurrentStage *DebugStage   `json:"-"`
	Debug        *Debugger     `json:"-"`
	Request      interface{}   `json:"request"`
}

func NewDebugTaks() *DebugTaks {
	return &DebugTaks{
		Stages:       []*DebugStage{},
		CurrentStage: &DebugStage{},
	}
}

func (debug *DebugTaks) Stage(id string) *DebugStage {
	stage := &DebugStage{}
	stage.ID = id
	stage.Events = []*DebugEvent{}
	debug.Stages = append(debug.Stages, stage)
	debug.CurrentStage = stage
	return stage
}

func (stage *DebugStage) PushEvent(event *DebugEvent) {
	stage.Events = append(stage.Events, event)
}

func (debug *DebugTaks) Event(eventType, eventId string) *DebugEvent {

	event := &DebugEvent{
		ID:      eventId,
		Type:    eventType,
		Created: time.Now().Unix(),
	}
	debug.CurrentStage.PushEvent(event)
	return event
}

func (task *DebugTaks) Finalize() {
	var (
		debug = task.Debug
		out   []byte
		err   error
	)

	for _, stage := range task.Stages {
		for _, event := range stage.Events {
			if event.Error != nil  {
				event.Status = "error"
				stage.Status = "error"
				task.Status = "error"
				goto end
			}
		}
	}
	// task.Event()
	end:
	if out, err = json.Marshal(task); err != nil {
		debug.Emit(&sse.Event{
			Kind:    "debugger.error",
			Payload: err.Error(),
		})
	} else {
		debug.Emit(&sse.Event{
			Kind:    "request",
			Payload: string(out),
		})
	}
}

type Debugger struct {
	ChannelID string
	Tasks     []*DebugTaks
	Hub       *sse.SSEHub
}

func (debug *Debugger) Handler() func(context.Context) {
	return func(ctx context.Context) {
		var err error
		task := debug.CreateTask()

		if task.Request, err = parseRequest(ctx.Request()); err != nil {
			fmt.Println("Request debug error", err.Error())
		}

		ctx.Values().Set("#debug", task)
		ctx.Next()
	}
}

func (debug *Debugger) CreateTask() *DebugTaks {
	task := &DebugTaks{
		ID:      primitive.NewObjectID().Hex(),
		Status:  "",
		Created: time.Now().Unix(),
		Debug:   debug,
	}
	debug.Tasks = append([]*DebugTaks{task}, debug.Tasks...)
	return task
}

func (debug *Debugger) Emit(events ...*sse.Event) {
	channel := debug.Hub.GetChannel(debug.ChannelID)
	for _, event := range events {
		channel.Emit(event)
	}
}

func (debug *Debugger) EventStream() func(context.Context) (interface{}, *errs.Error) {
	return func(ctx context.Context) (resp interface{}, err *errs.Error) {

		go func() {
			if len(debug.Tasks) > 0 {
				time.Sleep(time.Second)
				fmt.Println("initialize debug with ", len(debug.Tasks))
				for _, task := range debug.Tasks[:10] {
					task.Finalize()
				}
				debug.Tasks = debug.Tasks[:10]
			}
		}()

		if err = debug.Hub.UpgradeConnection(
			ctx,
			debug.ChannelID,
		); err != nil {
			return
		}

		resp = true
		return
	}
}

func NewDebug() *Debugger {
	return &Debugger{
		ChannelID: "debug",
		Tasks:     []*DebugTaks{},
		Hub: sse.NewSSEHub(&sse.SSEOptions{
			URI:               os.Getenv("REDIS_URI"),
			Password:          os.Getenv("REDIS_PASSWD"),
			ChannelCollection: "debugger",
		}),
	}
}
func parseRequest(req *http.Request) (result map[string]interface{}, err error) {
	save := req.Body
	defer func() {
		req.Body = save
	}()

	result = map[string]interface{}{}
	// if !body || req.Body == nil {
	// 	req.Body = nil
	// } else {
	// 	save, req.Body, err = drainBody(req.Body)
	// 	if err != nil {
	// 		return nil, err
	// 	}
	// }
	var (
		reqURI    string
		reqMethod string
	)

	if reqURI = req.RequestURI; reqURI == "" {
		reqURI = req.URL.RequestURI()
	}

	if reqMethod = req.Method; req.Method == "" {
		reqMethod = "GET"
	}

	result["URI"] = reqURI
	result["URL"] = map[string]interface{}{
		"Scheme":      req.URL.Scheme,
		"Host":        req.URL.Hostname(),
		"IsAbs":       req.URL.IsAbs(),
		"EscapedPath": req.URL.EscapedPath(),
		"Port":        req.URL.Port(),
		"Uri":         req.URL.String(),
		"query":       req.URL.Query(),
	}
	result["Method"] = reqMethod
	result["ProtoMajor"] = req.ProtoMajor
	result["ProtoMinor"] = req.ProtoMinor
	result["Proto"] = req.Proto
	result["RemoteAddr"] = req.RemoteAddr
	result["Header"] = req.Header
	result["Host"] = req.Host
	result["Close"] = req.Close
	result["ContentLength"] = req.ContentLength

	// for name, value := range req.Header {
	// 	fmt.Printf("%v: %v\n", name, value)
	// }

	// fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
	// reqURI, req.ProtoMajor, req.ProtoMinor)

	// absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://")
	// if !absRequestURI {
	// 	host := req.Host
	// 	if host == "" && req.URL != nil {
	// 		host = req.URL.Host
	// 	}
	// 	if host != "" {
	// 		fmt.Fprintf(&b, "Host: %s\r\n", host)
	// 	}
	// }

	// chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
	// if len(req.TransferEncoding) > 0 {
	// 	fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
	// }
	// if req.Close {
	// 	fmt.Fprintf(&b, "Connection: close\r\n")
	// }

	// err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
	// if err != nil {
	// 	return nil, err
	// }
	// io.WriteString(&b, "\r\n")
	// if req.Body != nil {
	// 	var dest io.Writer = &b
	// 	if chunked {
	// 		dest = NewChunkedWriter(dest)
	// 	}
	// 	_, err = io.Copy(dest, req.Body)
	// 	if chunked {
	// 		dest.(io.Closer).Close()
	// 		io.WriteString(&b, "\r\n")
	// 	}
	// }
	// req.Body = save
	// if err != nil {
	// 	return nil, err
	// }
	// return b.Bytes(), nil
	return
}
