package server

import (
	"mime"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"goyco/internal/config"
	"goyco/internal/handlers"
	"goyco/internal/middleware"

	"github.com/go-chi/chi/v5"
	httpSwagger "github.com/swaggo/http-swagger"
)

type RouterConfig struct {
	AuthHandler        *handlers.AuthHandler
	PostHandler        *handlers.PostHandler
	VoteHandler        *handlers.VoteHandler
	UserHandler        *handlers.UserHandler
	APIHandler         *handlers.APIHandler
	AuthService        middleware.TokenVerifier
	PageHandler        *handlers.PageHandler
	StaticDir          string
	Debug              bool
	DisableCache       bool
	DisableCompression bool
	CacheablePaths     []string
	DBMonitor          middleware.DBMonitor
	RateLimitConfig    config.RateLimitConfig
}

func swaggerExposed() bool {
	if strings.EqualFold(strings.TrimSpace(os.Getenv("SWAGGER_ENABLED")), "true") {
		return true
	}
	return strings.ToLower(strings.TrimSpace(os.Getenv("GOYCO_ENV"))) != "production"
}

func NewRouter(cfg RouterConfig) http.Handler {
	middleware.SetTrustProxyHeaders(cfg.RateLimitConfig.TrustProxyHeaders)

	exposeSwagger := swaggerExposed()

	router := chi.NewRouter()
	router.Use(middleware.Logging(cfg.Debug))
	router.Use(middleware.SecurityHeadersMiddlewareWithConfig(middleware.SecurityHeadersConfig{
		RelaxSwaggerCSP: exposeSwagger,
	}))
	router.Use(middleware.HSTSMiddleware())
	router.Use(middleware.CORS)

	if !cfg.DisableCompression {
		router.Use(middleware.CompressionMiddleware())
		router.Use(middleware.DecompressionMiddleware())
	}
	router.Use(middleware.DefaultRequestSizeLimitMiddleware())

	if !cfg.DisableCache {
		cache := middleware.NewInMemoryCache()
		cacheConfig := middleware.DefaultCacheConfig()
		if len(cfg.CacheablePaths) > 0 {
			cacheConfig.CacheablePaths = append([]string{}, cfg.CacheablePaths...)
		}
		router.Use(middleware.CacheMiddleware(cache, cacheConfig))
		router.Use(middleware.CacheInvalidationMiddleware(cache, cacheConfig.CacheablePaths))
	}

	var dbMonitor middleware.DBMonitor
	if cfg.DBMonitor != nil {
		dbMonitor = cfg.DBMonitor
	} else {
		dbMonitor = middleware.NewInMemoryDBMonitor()
	}

	router.Use(middleware.DBMonitoringMiddleware(dbMonitor, 100*time.Millisecond))

	metricsCollector := middleware.NewMetricsCollector(dbMonitor)
	router.Use(middleware.MetricsMiddleware(metricsCollector))

	routeConfig := handlers.RouteModuleConfig{
		AuthService: cfg.AuthService,
		GeneralRateLimit: func(r chi.Router) chi.Router {
			return r.With(middleware.GeneralRateLimitMiddlewareWithLimit(cfg.RateLimitConfig.GeneralLimit))
		},
		AuthRateLimit: func(r chi.Router) chi.Router {
			return r.With(middleware.AuthRateLimitMiddlewareWithLimit(cfg.RateLimitConfig.AuthLimit))
		},
		CSRFMiddleware:       middleware.CSRFMiddleware(),
		AuthMiddleware:       middleware.NewAuth(cfg.AuthService),
		ValidationMiddleware: middleware.ValidationMiddleware(),
	}

	if cfg.PageHandler != nil {
		cfg.PageHandler.MountRoutes(router, routeConfig)
	}

	if cfg.APIHandler != nil {
		healthRateLimited := router.With(middleware.HealthRateLimitMiddleware(cfg.RateLimitConfig.HealthLimit))
		healthRateLimited.Get("/health", cfg.APIHandler.GetHealth)

		metricsRateLimited := router.With(middleware.MetricsRateLimitMiddleware(cfg.RateLimitConfig.MetricsLimit))
		metricsRateLimited.Get("/metrics", cfg.APIHandler.GetMetrics)
	}

	if exposeSwagger {
		swaggerRateLimited := router.With(middleware.GeneralRateLimitMiddlewareWithLimit(cfg.RateLimitConfig.GeneralLimit))
		swaggerRateLimited.Get("/swagger/*", httpSwagger.Handler())
	}

	router.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, filepath.Join(cfg.StaticDir, "robots.txt"))
	})

	router.Route("/api", func(api chi.Router) {
		modules := []handlers.RouteModule{}

		if cfg.AuthHandler != nil {
			modules = append(modules, cfg.AuthHandler)
		}
		if cfg.PostHandler != nil {
			modules = append(modules, cfg.PostHandler)
		}
		if cfg.VoteHandler != nil {
			modules = append(modules, cfg.VoteHandler)
		}
		if cfg.UserHandler != nil {
			modules = append(modules, cfg.UserHandler)
		}
		if cfg.APIHandler != nil {
			modules = append(modules, cfg.APIHandler)
		}

		for _, module := range modules {
			module.MountRoutes(api, routeConfig)
		}
	})

	staticDir := cfg.StaticDir
	if staticDir == "" {
		staticDir = "./internal/static/"
	}

	staticFileServer := http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))
	router.Handle("/static/*", staticFileHandler(staticFileServer))

	return router
}

func staticFileHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		path := r.URL.Path
		ext := filepath.Ext(path)

		if ext == ".css" {
			w.Header().Set("Content-Type", "text/css; charset=utf-8")
		} else if ext == ".js" {
			w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
		} else if ext == ".json" {
			w.Header().Set("Content-Type", "application/json; charset=utf-8")
		} else if ext == ".ico" {
			w.Header().Set("Content-Type", "image/x-icon")
		} else if strings.HasPrefix(mime.TypeByExtension(ext), "image/") {
			w.Header().Set("Content-Type", mime.TypeByExtension(ext))
		} else if strings.HasPrefix(mime.TypeByExtension(ext), "font/") {
			w.Header().Set("Content-Type", mime.TypeByExtension(ext))
		} else if mimeType := mime.TypeByExtension(ext); mimeType != "" {
			w.Header().Set("Content-Type", mimeType)
		}

		next.ServeHTTP(w, r)
	})
}
