package middleware

import (
	"strings"

	"github.com/gofiber/fiber/v2"
	"github.com/tapper/backend/services"
)

func JWTAuth() fiber.Handler {
	return func(c *fiber.Ctx) error {
		var tokenStr string
		fromQuery := false

		auth := c.Get("Authorization")
		if auth != "" {
			parts := strings.SplitN(auth, " ", 2)
			if len(parts) == 2 && strings.ToLower(parts[0]) == "bearer" {
				tokenStr = parts[1]
			}
		}

		// EventSource cannot send Authorization headers, so SSE clients pass a
		// short-lived purpose=sse token in the query string. Full-purpose JWTs
		// are rejected via query to keep them out of access logs and Referer.
		if tokenStr == "" {
			tokenStr = c.Query("token")
			fromQuery = tokenStr != ""
		}

		if tokenStr == "" {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
				"error": "Missing authorization header",
			})
		}

		claims, err := services.ValidateAccessToken(tokenStr)
		if err != nil {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
				"error": "Invalid or expired token",
			})
		}

		if fromQuery && claims.Purpose != "sse" {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
				"error": "Query-string tokens are restricted to SSE",
			})
		}

		c.Locals("user_id", claims.UserID)
		c.Locals("username", claims.Username)
		c.Locals("role", claims.Role)
		c.Locals("tier", claims.Tier)
		c.Locals("steam_id", claims.SteamID)
		c.Locals("claims", claims)

		return c.Next()
	}
}

func OptionalJWTAuth() fiber.Handler {
	return func(c *fiber.Ctx) error {
		var tokenStr string

		auth := c.Get("Authorization")
		if auth != "" {
			parts := strings.SplitN(auth, " ", 2)
			if len(parts) == 2 && strings.ToLower(parts[0]) == "bearer" {
				tokenStr = parts[1]
			}
		}

		if tokenStr == "" {
			tokenStr = c.Query("token")
		}

		if tokenStr == "" {
			return c.Next()
		}

		claims, err := services.ValidateAccessToken(tokenStr)
		if err != nil {
			return c.Next()
		}

		c.Locals("user_id", claims.UserID)
		c.Locals("username", claims.Username)
		c.Locals("role", claims.Role)
		c.Locals("tier", claims.Tier)
		c.Locals("steam_id", claims.SteamID)
		c.Locals("claims", claims)

		return c.Next()
	}
}
