package http

import (
	"fmt"
	"io/ioutil"
	"net/http"

	"git.defalsify.org/vise.git/db"
	"git.defalsify.org/vise.git/engine"
	"git.defalsify.org/vise.git/logging"
	"git.defalsify.org/vise.git/persist"
	"git.defalsify.org/vise.git/resource"

	"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
)

var (
	logg = logging.NewVanilla().WithDomain("httpserver")
)

type RequestParser interface {
	GetSessionId(rq *http.Request) (string, error)
	GetInput(rq *http.Request) ([]byte, error)
}

type DefaultRequestParser struct {
}

func(rp *DefaultRequestParser) GetSessionId(rq *http.Request) (string, error) {
	v := rq.Header.Get("X-Vise-Session")
	if v == "" {
		return "", fmt.Errorf("no session found")
	}
	return v, nil
}

func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) {
	defer rq.Body.Close()
	v, err := ioutil.ReadAll(rq.Body)
	if err != nil {
		return nil, err
	}
	return v, nil
}

type SessionHandler struct {
	cfgTemplate engine.Config
	rp RequestParser
	rs resource.Resource
	//first resource.EntryFunc
	hn *ussd.Handlers
	provider StorageProvider
}

//func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, first resource.EntryFunc) *SessionHandler {
func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *SessionHandler {
	return &SessionHandler{
		cfgTemplate: cfg,
		rs: rs,
		//first: first,
		hn: hn,
		rp: rp,
		provider: NewSimpleStorageProvider(stateDb, userdataDb),
	}
}

func(f *SessionHandler) writeError(w http.ResponseWriter, code int, msg string, err error) {
	w.Header().Set("X-Vise", msg + ": " + err.Error())
	w.Header().Set("Content-Length", "0")
	w.WriteHeader(code)
	_, err = w.Write([]byte{})
	if err != nil {
		w.WriteHeader(500)
		w.Header().Set("X-Vise", err.Error())
	}
	return 
}

func(f* SessionHandler) Shutdown() {
	err := f.provider.Close()
	if err != nil {
		logg.Errorf("handler shutdown error", "err", err)
	}
}

func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var r bool
	sessionId, err := f.rp.GetSessionId(req)
	if err != nil {
		f.writeError(w, 400, "Session missing", err)
		return
	}
	input, err := f.rp.GetInput(req)
	if err != nil {
		f.writeError(w, 400, "Input read fail", err)
		return
	}
	ctx := req.Context()
	cfg := f.cfgTemplate
	cfg.SessionId = sessionId

	logg.InfoCtxf(ctx, "new request",  "session", cfg.SessionId, "input", input)

	storage, err := f.provider.Get(cfg.SessionId)
	if err != nil {
		f.writeError(w, 500, "Storage retrieval fail", err)
		return
	}
	f.hn = f.hn.WithPersister(storage.Persister)
	defer f.provider.Put(cfg.SessionId, storage)
	en := getEngine(cfg, f.rs, storage.Persister)
	en = en.WithFirst(f.hn.Init)
	if cfg.EngineDebug {
		en = en.WithDebug(nil)
	}

	r, err = en.Init(ctx)
	if err != nil {
		f.writeError(w, 500, "Engine init fail", err)
		return
	}
	if r && len(input) > 0 {
		r, err = en.Exec(ctx, input)
	}
	if err != nil {
		f.writeError(w, 500, "Engine exec fail", err)
		return
	}

	w.WriteHeader(200)
	w.Header().Set("Content-Type", "text/plain")
	_, err = en.WriteResult(ctx, w)
	if err != nil {
		f.writeError(w, 500, "Write result fail", err)
		return
	}
	err = en.Finish()
	if err != nil {
		f.writeError(w, 500, "Engine finish fail", err)
		return
	}

	_ = r
}

func getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *engine.DefaultEngine {
	en := engine.NewEngine(cfg, rs)
	en = en.WithPersister(pr)
	return en
}