package middleware

import (
	"fmt"
	"git.markbailey.dev/cervers/ptpp/lib/database"
	"net/http"
	"os"

	"git.markbailey.dev/cervers/ptpp/lib/logger"
	"git.markbailey.dev/cervers/ptpp/lib/session"
	"git.markbailey.dev/cervers/ptpp/util"
)

type Middleware struct {
	l  logger.ILogger
	db database.IDB
}

type (
	ErrHandler func(http.ResponseWriter, *http.Request) error
	Func       func(http.Handler) http.Handler
)

var (
	sess session.IManager
)

func (fn ErrHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := fn(w, r); err != nil {
		w.Header().Set("HX-Retarget", "#layout_content")
		w.Header().Set("HX-Reswap", "innerHTML")
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func Compose(xs ...Func) Func {
	return func(next http.Handler) http.Handler {
		for i := len(xs) - 1; i >= 0; i-- {
			x := xs[i]
			next = x(next)
		}

		return next
	}
}

func WithLogger(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		handlerSess := sess.SessionStart(w, r)

		username, ok := handlerSess.Get("username").(string)
		if !ok {
			username = "<Not Found>"
		}

		ipAddr := r.Header.Get("X-Real-IP")
		if ipAddr == "" {
			ipAddr = r.Header.Get("X-Forwarded-For")
		}
		if ipAddr == "" {
			ipAddr = r.RemoteAddr
		}

		handlerLogger := logger.NewCompositeLogger()
		output := fmt.Sprintf(
			"%s Request sent from %s to %s (username? %s)",
			r.Method,
			ipAddr,
			r.URL.Path,
			username,
		)
		handlerLogger.Info(output)

		next.ServeHTTP(w, r)
	})
}

func WithAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var (
			claims      *util.CustomClaims
			cookie      *http.Cookie
			err         error
			token       string
			handlerSess = sess.SessionStart(w, r)
		)

		if handlerSess.Get("username") != nil {
			req := util.AddValuesToRequestContext(r, map[any]any{
				"username": handlerSess.Get("username"),
			})
			next.ServeHTTP(w, req)
			return
		}

		if cookie, err = r.Cookie("token"); err != nil {
			_ = util.Redirect(w, r, "/signin", http.StatusSeeOther, true)
			return
		}
		if token = cookie.Value; token == "" {
			_ = util.Redirect(w, r, "/signin", http.StatusSeeOther, true)
			return
		}
		if claims, err = util.ParseToken(token, os.Getenv("TOKEN_SECRET")); err != nil {
			_ = util.Redirect(w, r, "/signin", http.StatusSeeOther, true)
			return
		}

		req := util.AddValuesToRequestContext(r, map[any]any{
			"username": claims.Username,
		})
		next.ServeHTTP(w, req)
	})
}

func WithUsername(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var (
			claims      *util.CustomClaims
			cookie      *http.Cookie
			err         error
			token       string
			handlerSess = sess.SessionStart(w, r)
		)

		if handlerSess.Get("username") != nil {
			req := util.AddValuesToRequestContext(r, map[any]any{
				"username": handlerSess.Get("username"),
			})
			next.ServeHTTP(w, req)
			return
		}

		var uname *string
		if cookie, err = r.Cookie("token"); err == nil {
			if token = cookie.Value; token == "" {
				uname = nil
			}
			if claims, err = util.ParseToken(token, os.Getenv("TOKEN_SECRET")); err != nil {
				uname = nil
			}
			uname = &claims.Username
		}

		if uname != nil {
			req := util.AddValuesToRequestContext(r, map[any]any{
				"username": uname,
			})
			next.ServeHTTP(w, req)
			return
		}

		next.ServeHTTP(w, r)
	})
}

func init() {
	sess, _ = session.NewManager("memory", "ptpp", 3600)
}
