package middleware

import (
	"jaecoo-be/app/database"
	"jaecoo-be/config/config"
	utilsSvc "jaecoo-be/utils"
	"log"
	"time"

	"github.com/gofiber/fiber/v2/middleware/csrf"
	"github.com/gofiber/fiber/v2/middleware/session"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/compress"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/limiter"
	"github.com/gofiber/fiber/v2/middleware/monitor"
	"github.com/gofiber/fiber/v2/middleware/pprof"
	"github.com/gofiber/fiber/v2/middleware/recover"
	"github.com/gofiber/fiber/v2/utils"
)

// Middleware is a struct that contains all the middleware functions
type Middleware struct {
	App *fiber.App
	Cfg *config.Config
}

func NewMiddleware(app *fiber.App, cfg *config.Config) *Middleware {
	return &Middleware{
		App: app,
		Cfg: cfg,
	}
}

// Register registers all the middleware functions
func (m *Middleware) Register(db *database.Database) {
	// Add Extra Middlewares

	m.App.Use(limiter.New(limiter.Config{
		Next:       utilsSvc.IsEnabled(m.Cfg.Middleware.Limiter.Enable),
		Max:        m.Cfg.Middleware.Limiter.Max,
		Expiration: m.Cfg.Middleware.Limiter.Expiration * time.Second,
	}))

	m.App.Use(compress.New(compress.Config{
		Next:  utilsSvc.IsEnabled(m.Cfg.Middleware.Compress.Enable),
		Level: m.Cfg.Middleware.Compress.Level,
	}))

	m.App.Use(recover.New(recover.Config{
		Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Recover.Enable),
	}))

	m.App.Use(pprof.New(pprof.Config{
		Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Pprof.Enable),
	}))

	m.App.Use(cors.New(cors.Config{
		Next:             utilsSvc.IsEnabled(m.Cfg.Middleware.Cors.Enable),
		AllowOrigins:     "http://localhost:3000, http://localhost:4000, https://dev.mikulnews.com, https://n8n.qudoco.com, https://campaignpool.id, https://jaecoocihampelasbdg.com, https://jaecookelapagading.com",
		AllowMethods:     "HEAD, GET, POST, PUT, DELETE, OPTION, PATCH",
		AllowHeaders:     "Origin, Content-Type, Accept, Accept-Language, Authorization, X-Requested-With, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Allow-Origin, Access-Control-Allow-Credentials, X-Csrf-Token, Cookie, Set-Cookie, X-Client-Key",
		ExposeHeaders:    "Content-Length, Content-Type",
		AllowCredentials: true,
		MaxAge:           12,
	}))

	//===============================
	// CSRF CONFIG
	//===============================

	// Only setup CSRF middleware if enabled
	if m.Cfg.Middleware.Csrf.Enable {
		// Custom storage for CSRF
		csrfSessionStorage := &PostgresStorage{
			DB: db.DB,
		}

		// Store initialization for session
		store := session.New(session.Config{
			CookieSameSite:    m.Cfg.Middleware.Csrf.CookieSameSite,
			CookieSecure:      m.Cfg.Middleware.Csrf.CookieSecure,
			CookieSessionOnly: m.Cfg.Middleware.Csrf.CookieSessionOnly,
			CookieHTTPOnly:    m.Cfg.Middleware.Csrf.CookieHttpOnly,
			Storage:           csrfSessionStorage,
		})

		m.App.Use(func(c *fiber.Ctx) error {
			sess, err := store.Get(c)
			if err != nil {
				return err
			}
			c.Locals("session", sess)
			return c.Next()
		})

		// Cleanup the expired token
		go func() {
			ticker := time.NewTicker(1 * time.Hour)
			defer ticker.Stop()

			for range ticker.C {
				if err := csrfSessionStorage.Reset(); err != nil {
					log.Printf("Error cleaning up expired CSRF tokens: %v", err)
				}
			}
		}()

		m.App.Use(csrf.New(csrf.Config{
			KeyLookup:         "header:" + csrf.HeaderName,
			CookieName:        m.Cfg.Middleware.Csrf.CookieName,
			CookieSameSite:    m.Cfg.Middleware.Csrf.CookieSameSite,
			CookieSecure:      m.Cfg.Middleware.Csrf.CookieSecure,
			CookieSessionOnly: m.Cfg.Middleware.Csrf.CookieSessionOnly,
			CookieHTTPOnly:    m.Cfg.Middleware.Csrf.CookieHttpOnly,
			Expiration:        1 * time.Hour,
			KeyGenerator:      utils.UUIDv4,
			ContextKey:        "csrf",
			ErrorHandler: func(c *fiber.Ctx, err error) error {
				return utilsSvc.CsrfErrorHandler(c, err)
			},
			Extractor:  csrf.CsrfFromHeader(csrf.HeaderName),
			Session:    store,
			SessionKey: "fiber.csrf.token",
		}))
	}

	//===============================
	m.App.Use(AuditTrailsMiddleware(db.DB))
	StartAuditTrailCleanup(db.DB, m.Cfg.Middleware.AuditTrails.Retention)

	//m.App.Use(filesystem.New(filesystem.Config{
	//	Next:   utils.IsEnabled(m.Cfg.Middleware.FileSystem.Enable),
	//	Root:   http.Dir(m.Cfg.Middleware.FileSystem.Root),
	//	Browse: m.Cfg.Middleware.FileSystem.Browse,
	//	MaxAge: m.Cfg.Middleware.FileSystem.MaxAge,
	//}))

	// ==================================================

	m.App.Get(m.Cfg.Middleware.Monitor.Path, monitor.New(monitor.Config{
		Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Monitor.Enable),
	}))

	// Route for generate CSRF token (only available if CSRF is enabled)
	m.App.Get("/csrf-token", func(c *fiber.Ctx) error {
		// Retrieve CSRF token from Fiber's middleware context
		token, ok := c.Locals("csrf").(string)

		//c.Context().VisitUserValues(func(key []byte, value interface{}) {
		//	log.Printf("Local Key: %s, Value: %v", key, value)
		//})

		if !ok || token == "" {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
				"success":  false,
				"code":     500,
				"messages": []string{"Failed to retrieve CSRF token"},
			})
		}

		return c.JSON(fiber.Map{
			"success":    true,
			"csrf_token": token,
		})
	})
}
