package server

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	domain "github.com/haydenhargreaves/Potion/internal/domain/server"
	"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
)

// JwtAuthMiddlewareV2 is responsible for protecting routes. Anything that may go wrong
// will be returned via JSON with a 'message' field and a 401 error code. When this
// middleware is successful, it will set the 'userId' and 'userEmail' fields and pass
// to the next function in the chain.
//
// Functions that are called after this can assume that those values defined are always
// set.
func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		tokenString, err := ctx.Cookie("jwt_token")
		if err != nil {
			ctx.JSON(http.StatusUnauthorized, gin.H{
				"status":  http.StatusUnauthorized,
				"message": fmt.Sprintf("[UNAUTHORIZED] Failed to get token from cookie. %s", err.Error()),
			})
			ctx.Abort()
			return
		}

		claims := &domain.JwtClaims{}

		token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
			}
			return jwtSecretKey, nil
		})

		// Error occurred when parsing
		if err != nil {
			ctx.JSON(http.StatusUnauthorized, gin.H{
				"status":  http.StatusUnauthorized,
				"message": fmt.Sprintf("[UNAUTHORIZED] Error parsing cooking. %s", err.Error()),
			})
			ctx.Abort()
			return
		}

		// Token is invalid
		if !token.Valid {
			ctx.JSON(http.StatusUnauthorized, gin.H{
				"status":  http.StatusUnauthorized,
				"message": "[UNAUTHORIZED] Token is invalid.",
			})
			ctx.Abort()
			return
		}

		// Found: Set the values
		ctx.Set("userId", claims.UserId)
		ctx.Set("userEmail", claims.Email)
		ctx.Next()
	}
}

// JwtOptionalAuthMiddlewareV2 is responsible for collecting user data for routes where
// authentication is optional. Meaning: if the use is not logged in, this function does
// not fail or return, it simply does nothing. But if the user is logged in, then the
// 'userId' and 'userEmail' context values are set.
//
// e.g., `userIdAny, exists := ctx.Get("userId")`
func JwtOptionalAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		tokenString, err := ctx.Cookie("jwt_token")
		if err != nil || tokenString == "" {
			// No cookie found: not authenticated, but allow access
			ctx.Next()
			return
		}

		claims := &domain.JwtClaims{}
		token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
			}
			return jwtSecretKey, nil
		})

		if err == nil && token.Valid {
			// Set user info in context if token is valid
			ctx.Set("userId", claims.UserId)
			ctx.Set("userEmail", claims.Email)
		}
		// Otherwise, just continue (user is unauthenticated)
		ctx.Next()
	}
}

func LoggingMiddleware(logs []logging.Logger) gin.HandlerFunc {
	// TODO: Need traces using IDs?
	return func(ctx *gin.Context) {
		start := time.Now()

		ctx.Next()

		var (
			status  int    = ctx.Writer.Status()
			latency string = time.Since(start).String()
			client  string = ctx.ClientIP()
			method  string = ctx.Request.Method
			path    string = ctx.Request.URL.Path
		)

		// TODO: Add color to status

		format := "%d | %-14s | %15s | %-9s \"%s\""
		logging.LogAll(
			logs,
			logging.LogLevelInfo,
			format,
			status,
			latency,
			client,
			method,
			path,
		)
	}
}
