package middleware

import (
	"fmt"
	"net/http"
	"os"

	"git.markbailey.dev/cerbervs/ptpp/app/session"
	"git.markbailey.dev/cerbervs/ptpp/util"

	"git.markbailey.dev/cerbervs/ptpp/lib/logger"
)

type IRouterCtx interface {
	GetRouteByName(string) (string, error)
}

type IMiddlewareCtx interface {
	GetRouteByName(string) string
}

type MiddlewareCtx struct {
	RouterCtx IRouterCtx
}

func (m MiddlewareCtx) GetRouteByName(name string) string {
	path, err := m.RouterCtx.GetRouteByName(name)
	if err != nil {
		panic(err)
	}

	return path
}

type Func func(http.Handler, IMiddlewareCtx) http.Handler

var sess session.IManager

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

		return next
	}
}

func DontPanic(next http.Handler, m IMiddlewareCtx) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if r := recover(); r != nil {
				http.Error(w, fmt.Sprintf("%v", r), http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}

func WithLogger(next http.Handler, m IMiddlewareCtx) 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 = ""
		}
		username = "(username " + username + ")"

		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 %s",
			r.Method,
			ipAddr,
			r.URL.Path,
			username,
		)
		handlerLogger.Info(output)

		next.ServeHTTP(w, r)
	})
}

func WithAuth(next http.Handler, m IMiddlewareCtx) 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, m.GetRouteByName("app.user.sign_in"), http.StatusPermanentRedirect, true)
			return
		}
		if token = cookie.Value; token == "" {
			_ = util.Redirect(w, r, m.GetRouteByName("app.user.sign_in"), http.StatusPermanentRedirect, true)
			return
		}
		if claims, err = util.ParseToken(token, os.Getenv("TOKEN_SECRET")); err != nil {
			_ = util.Redirect(w, r, m.GetRouteByName("app.user.sign_in"), http.StatusPermanentRedirect, true)
			return
		}

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

func WithUsername(next http.Handler, m IMiddlewareCtx) 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() {
	var err error
	sess, err = session.NewManager("memory", "ptpp", 3600)
	if err != nil {
		panic(err)
	}
}
