package dart

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

	"github.com/gorilla/websocket"
	"github.com/julienschmidt/httprouter"
	"github.com/robfig/cron/v3"
)

type Dart struct {
	router       *httprouter.Router
	errorHandler ErrorHandler
	middlewares  []Middleware
	logger       *log.Logger

	database       *Database
	enableDatabase bool

	websocketManager       *WebsocketManager
	enableWebsocketManager bool

	cron                 *cron.Cron
	enableCloudFunctions bool
}

func New() *Dart {
	return &Dart{
		router:       httprouter.New(),
		errorHandler: defaultErrorHandler,
		middlewares:  []Middleware{},
		logger:       log.New(os.Stdout, "[Dart] ", log.LstdFlags),
	}
}

func (d *Dart) WithMySQLDatabase(cfg MySQLConfig) {
	d.database = NewMySQLDatabase(cfg)
	d.enableDatabase = true
}

func (d *Dart) WithPostgreSQLDatabase(cfg PostgreSQLConfig) {
	d.database = NewPostgreSQLDatabase(cfg)
	d.enableDatabase = true
}

func (d *Dart) WithSQLiteDatabase(cfg SQLiteConfig) {
	d.database = NewSQLiteDatabase(cfg)
	d.enableDatabase = true
}

func (d *Dart) DB() (*Database, error) {
	if d.database == nil {
		return nil, errors.New("database is not enabled")
	}

	return d.database, nil
}

func (d *Dart) WithWebsocketManager() {
	d.websocketManager = &WebsocketManager{
		conns: make(map[string]map[*websocket.Conn]bool),
	}
	d.enableWebsocketManager = true
}

func (d *Dart) WithCloudFunctions() {
	d.cron = cron.New()
	d.enableCloudFunctions = true
}

func (d *Dart) ScheduleCloudFunction(spec string, cf *CloudFunction) {
	if !d.enableCloudFunctions {
		d.logger.Println("Cloud function scheduling attempted but cloud functions are disabled")
		return
	}

	ctx := &CloudContext{
		database:         d.database,
		websocketManager: d.websocketManager,
		state:            make(map[string]any),
	}

	entryId, err := d.cron.AddFunc(spec, func() {
		d.logger.Printf("Executing cloud function: %s", cf.Name)
		if err := cf.Handler(ctx); err != nil {
			d.logger.Printf("Error executing cloud function '%s': %v\n", cf.Name, err)
		}
	})
	if err != nil {
		d.logger.Printf("Error scheduling cloud function '%s': '%v\n", cf.Name, entryId)
		return
	}
	cf.EntryID = entryId
}

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

	if d.enableCloudFunctions {
		d.cron.Start()
		defer d.cron.Stop()
	}

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

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

	if d.enableCloudFunctions {
		d.cron.Start()
		defer d.cron.Stop()
	}

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

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

func (d *Dart) Group(prefix string) *Group {
	return &Group{
		prefix: prefix,
		dart:   d,
	}
}

func (d *Dart) Websocket(path string, middlewares ...Middleware) {
	if !d.enableWebsocketManager {
		d.logger.Println("Websocket registration attempted but websockets are disabled")
		return
	}

	d.register(http.MethodGet, path, d.websocketManager.createHandler(), middlewares...)
}

func (d *Dart) 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 *Dart) Use(middlewares ...Middleware) {
	d.middlewares = append(d.middlewares, middlewares...)
}

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

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

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

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

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

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

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

func (d *Dart) 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 *Dart) 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(),
			database:         d.database,
			websocketManager: d.websocketManager,
		}

		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)
		}
	}
}