package routing

import (
	"errors"
	"net/http"
	"sync"

	"git.markbailey.dev/cerbervs/ptpp/app/controller"
	"git.markbailey.dev/cerbervs/ptpp/app/handler"
	"git.markbailey.dev/cerbervs/ptpp/app/session"
	"git.markbailey.dev/cerbervs/ptpp/lib/database"
	werror "git.markbailey.dev/cerbervs/ptpp/lib/error"
	"git.markbailey.dev/cerbervs/ptpp/lib/logger"
	"git.markbailey.dev/cerbervs/ptpp/lib/middleware"
	"git.markbailey.dev/cerbervs/ptpp/util"
)

var sess session.IManager

type IRouter interface {
	RegisterRoutes() http.Handler
}

type Route struct {
	Controller controller.IController
	Path       string
	Name       string
}

type Router struct {
	Mux        *http.ServeMux
	Middleware *[]middleware.Func
	BaseRoute  Route
	BasePath   string
	SubRouters []*Router
	Routes     []Route
}

var (
	rtr     *Router
	rtrOnce sync.Once
	mux     *http.ServeMux
	muxOnce sync.Once
	fsOnce  sync.Once
)

func (r *Router) HandleAllRequestMethods(route Route, fullPath string, mux *http.ServeMux) {
	ctx := controller.ControllerCtx{RouterCtx: rtr}
	c := route.Controller.Init(sess, database.ChooseDB(), logger.NewCompositeLogger(), ctx)
	mux.Handle("GET "+fullPath, handler.HandlerFunc(c.Get))
	mux.Handle("OPTIONS "+fullPath, handler.HandlerFunc(c.Options))
	mux.Handle("TRACE "+fullPath, handler.HandlerFunc(c.Trace))
	mux.Handle("PUT "+fullPath, handler.HandlerFunc(c.Put))
	mux.Handle("DELETE "+fullPath, handler.HandlerFunc(c.Delete))
	mux.Handle("POST "+fullPath, handler.HandlerFunc(c.Post))
	mux.Handle("PATCH "+fullPath, handler.HandlerFunc(c.Patch))
	mux.Handle("CONNECT "+fullPath, handler.HandlerFunc(c.Connect))
}

func (r *Router) RegisterRoutes() http.Handler {
	rtrOnce.Do(func() { rtr = r })
	muxOnce.Do(func() { mux = http.NewServeMux(); r.Mux = mux })
	fsOnce.Do(func() { r.RegisterFs() })

	if r.Mux == nil {
		r.Mux = http.NewServeMux()
	}

	for _, subRouter := range r.SubRouters {
		r.Mux.Handle(subRouter.BasePath, subRouter.RegisterRoutes())
	}

	r.Mux.Handle(r.BasePath+r.BaseRoute.Path, handler.HandlerFunc(r.BaseRoute.Controller.Get))
	for _, route := range r.Routes {
		r.HandleAllRequestMethods(route, r.BasePath+route.Path, r.Mux)
	}

	if r.Middleware != nil {
		mw := middleware.Compose(*r.Middleware)
		mctx := middleware.MiddlewareCtx{RouterCtx: rtr}
		return mw(mux, mctx)
	}

	return mux
}

func (r *Router) RegisterFs() {
	fs := http.FileServer(http.Dir(util.GetFullyQualifiedPath("/public")))
	r.Mux.Handle("GET "+r.BasePath+"public/", http.StripPrefix("/public/", fs))
}

type RouteMapping struct {
	Path string
	Name string
}

func (r *Router) GetFlatRouteList() []RouteMapping {
	routes := []RouteMapping{{Path: r.BasePath, Name: r.BaseRoute.Name}}
	for _, route := range r.Routes {
		routes = append(routes, RouteMapping{Path: r.BasePath + route.Path, Name: route.Name})
	}
	for _, subRouter := range r.SubRouters {
		routes = append(routes, subRouter.GetFlatRouteList()...)
	}
	return routes
}

func (r *Router) GetRouteByName(name string) (string, error) {
	for _, route := range r.GetFlatRouteList() {
		if route.Name == name {
			return route.Path, nil
		}
	}
	return "", werror.Wrap(errors.New(name+" does not exist"), "Route not found")
}

func init() {
	var err error

	sess, err = session.NewManager("memory", "ptpp", 3600)
	go sess.GC()
	if err != nil {
		panic(werror.Wrap(err, "Error creating session manager"))
	}
}
