package drip

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"slices"
	"strings"

	"github.com/gorilla/websocket"
	"github.com/julienschmidt/httprouter"
)

type Drip struct {
	router           *httprouter.Router
	errorHandler     ErrorHandler
	middlewares      []Middleware
	logger           *log.Logger
	websocketManager *WebsocketManager
}

func New() *Drip {
	return &Drip{
		router:       httprouter.New(),
		errorHandler: defaultErrorHandler,
		middlewares:  []Middleware{},
		logger:       log.New(os.Stdout, "[Drip] ", log.LstdFlags),
		websocketManager: &WebsocketManager{
			conns: make(map[string]map[*websocket.Conn]bool),
		},
	}
}

func (d *Drip) Start(listenAddr string) error {
	browsableUrl := listenAddr
	if strings.HasPrefix(browsableUrl, ":") {
		browsableUrl = fmt.Sprintf("http://localhost%s", browsableUrl)
	}

	d.logger.Printf("Starting app at %s\n", browsableUrl)
	return http.ListenAndServe(listenAddr, d.router)
}

func (d *Drip) StartTLS(listenAddr, certFile, keyFile string) error {
	browsableUrl := listenAddr
	if strings.HasPrefix(browsableUrl, ":") {
		browsableUrl = fmt.Sprintf("http://localhost%s", browsableUrl)
	}

	d.logger.Printf("Starting app at %s\n", browsableUrl)
	return http.ListenAndServeTLS(listenAddr, certFile, keyFile, d.router)
}

func (d *Drip) SetErrorHandler(errorHandler ErrorHandler) {
	d.errorHandler = errorHandler
}

func (d *Drip) Group(prefix string) *Group {
	return &Group{
		prefix: prefix,
		drip:   d,
	}
}

func (d *Drip) Websocket(path string, middlewares ...Middleware) {
	d.register(http.MethodGet, path, d.websocketManager.createHandler(), middlewares...)
}

func (d *Drip) Static(prefix, root string, middlewares ...Middleware) {
	fileServer := http.FileServer(http.Dir(root))
	wrappedHandler := d.createHandlerChain(func(ctx *Context) error {
		http.StripPrefix(prefix, fileServer).ServeHTTP(ctx.response, ctx.request)
		return nil
	}, middlewares)

	d.router.Handle(http.MethodGet, prefix+"/*filepath", wrappedHandler)
}

func (d *Drip) Use(middlewares ...Middleware) {
	d.middlewares = append(d.middlewares, middlewares...)
}

func (d *Drip) GET(path string, h Handler, middlewares ...Middleware) {
	d.register(http.MethodGet, path, h, middlewares...)
}

func (d *Drip) POST(path string, h Handler, middlewares ...Middleware) {
	d.register(http.MethodPost, path, h, middlewares...)
}

func (d *Drip) PUT(path string, h Handler, middlewares ...Middleware) {
	d.register(http.MethodPut, path, h, middlewares...)
}

func (d *Drip) DELETE(path string, h Handler, middlewares ...Middleware) {
	d.register(http.MethodDelete, path, h, middlewares...)
}

func (d *Drip) PATCH(path string, h Handler, middlewares ...Middleware) {
	d.register(http.MethodPatch, path, h, middlewares...)
}

func (d *Drip) HEAD(path string, h Handler, middlewares ...Middleware) {
	d.register(http.MethodHead, path, h, middlewares...)
}

func (d *Drip) OPTIONS(path string, h Handler, middlewares ...Middleware) {
	d.register(http.MethodOptions, path, h, middlewares...)
}

func (d *Drip) register(method string, path string, h Handler, middlewares ...Middleware) {
	handlers := slices.Clone(middlewares)

	finalHandler := d.createHandlerChain(h, handlers)
	d.router.Handle(method, path, finalHandler)
}

func (d *Drip) createHandlerChain(h Handler, middlewares []Middleware) httprouter.Handle {
	return func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) {
		ctx := &Context{
			response: rw,
			request:  r,
			params:   params,
			ctx:      context.Background(),
		}

		finalHandler := h

		for i := len(middlewares) - 1; i >= 0; i-- {
			finalHandler = middlewares[i](finalHandler)
		}

		for i := len(d.middlewares) - 1; i >= 0; i-- {
			finalHandler = d.middlewares[i](finalHandler)
		}

		if err := finalHandler(ctx); err != nil {
			d.logger.Printf("Error in request %s %s: %v\n", r.Method, r.URL.Path, err)
			d.errorHandler(err, ctx)
		}
	}
}