package middleware

import (
	"net/http"
	"strconv"
	"slices"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/tdvorak/seen/backend/internal/config"
)

func CORS(cfg config.CORSConfig) gin.HandlerFunc {
	allowedOrigins := normalizeHeaderValues(cfg.AllowedOrigins)
	allowedMethods := normalizeHeaderValues(cfg.AllowedMethods)
	allowedHeaders := normalizeHeaderValues(cfg.AllowedHeaders)
	exposedHeaders := normalizeHeaderValues(cfg.ExposedHeaders)

	allowAnyOrigin := slices.Contains(allowedOrigins, "*")
	allowedMethodsValue := strings.Join(allowedMethods, ", ")
	allowedHeadersValue := strings.Join(allowedHeaders, ", ")
	exposedHeadersValue := strings.Join(exposedHeaders, ", ")

	return func(c *gin.Context) {
		origin := strings.TrimSpace(c.GetHeader("Origin"))
		if origin == "" {
			c.Next()
			return
		}

		if !allowAnyOrigin && !slices.Contains(allowedOrigins, origin) {
			if c.Request.Method == http.MethodOptions {
				c.AbortWithStatus(http.StatusForbidden)
				return
			}

			c.Next()
			return
		}

		if allowAnyOrigin && !cfg.AllowCredentials {
			c.Header("Access-Control-Allow-Origin", "*")
		} else {
			c.Header("Access-Control-Allow-Origin", origin)
			c.Header("Vary", "Origin")
		}

		c.Header("Access-Control-Allow-Methods", allowedMethodsValue)
		c.Header("Access-Control-Allow-Headers", allowedHeadersValue)
		if exposedHeadersValue != "" {
			c.Header("Access-Control-Expose-Headers", exposedHeadersValue)
		}
		if cfg.AllowCredentials {
			c.Header("Access-Control-Allow-Credentials", "true")
		}
		if cfg.MaxAge > 0 {
			c.Header("Access-Control-Max-Age", formatSeconds(cfg.MaxAge.Seconds()))
		}

		if c.Request.Method == http.MethodOptions {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}

		c.Next()
	}
}

func normalizeHeaderValues(values []string) []string {
	items := make([]string, 0, len(values))
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		items = append(items, trimmed)
	}

	return items
}

func formatSeconds(value float64) string {
	return strconv.Itoa(int(value))
}
