package middleware

import (
	"github.com/gin-gonic/gin"
	"github.com/pocket-id/pocket-id/backend/internal/apikey"
	"github.com/pocket-id/pocket-id/backend/internal/apperror"
	"github.com/pocket-id/pocket-id/backend/internal/service"
)

// AuthMiddleware is a wrapper middleware that delegates to either API key or JWT authentication
type AuthMiddleware struct {
	apiKeyMiddleware *ApiKeyAuthMiddleware
	jwtMiddleware    *JwtAuthMiddleware
	options          AuthOptions
}

type AuthOptions struct {
	AdminRequired   bool
	SuccessOptional bool
	AllowApiKeyAuth bool
}

func NewAuthMiddleware(
	apiKeyModule *apikey.Module,
	userService *service.UserService,
	jwtService *service.JwtService,
) *AuthMiddleware {
	return &AuthMiddleware{
		apiKeyMiddleware: NewApiKeyAuthMiddleware(apiKeyModule, jwtService),
		jwtMiddleware:    NewJwtAuthMiddleware(jwtService, userService),
		options: AuthOptions{
			AdminRequired:   true,
			SuccessOptional: false,
			AllowApiKeyAuth: true,
		},
	}
}

// WithAdminNotRequired allows the middleware to continue with the request even if the user is not an admin
func (m *AuthMiddleware) WithAdminNotRequired() *AuthMiddleware {
	// Create a new instance to avoid modifying the original
	clone := &AuthMiddleware{
		apiKeyMiddleware: m.apiKeyMiddleware,
		jwtMiddleware:    m.jwtMiddleware,
		options:          m.options,
	}
	clone.options.AdminRequired = false
	return clone
}

// WithSuccessOptional allows the middleware to continue with the request even if authentication fails
func (m *AuthMiddleware) WithSuccessOptional() *AuthMiddleware {
	// Create a new instance to avoid modifying the original
	clone := &AuthMiddleware{
		apiKeyMiddleware: m.apiKeyMiddleware,
		jwtMiddleware:    m.jwtMiddleware,
		options:          m.options,
	}
	clone.options.SuccessOptional = true
	return clone
}

// WithApiKeyAuthDisabled disables API key authentication fallback and requires JWT auth.
func (m *AuthMiddleware) WithApiKeyAuthDisabled() *AuthMiddleware {
	clone := &AuthMiddleware{
		apiKeyMiddleware: m.apiKeyMiddleware,
		jwtMiddleware:    m.jwtMiddleware,
		options:          m.options,
	}
	clone.options.AllowApiKeyAuth = false
	return clone
}

func (m *AuthMiddleware) Add() gin.HandlerFunc {
	return func(c *gin.Context) {
		userID, isAdmin, authenticationMethod, authenticationTime, err := m.jwtMiddleware.Verify(c, m.options.AdminRequired)
		if err == nil {
			c.Set("userID", userID)
			c.Set("userIsAdmin", isAdmin)
			c.Set("authenticationMethod", authenticationMethod)
			c.Set("authenticationTime", authenticationTime)
			if c.IsAborted() {
				return
			}
			c.Next()
			return
		}

		// If JWT auth failed for a reason other than missing credentials, abort the request
		if !apperror.IsCode(err, apperror.CodeNotSignedIn) {
			c.Abort()
			_ = c.Error(err)
			return
		}

		if !m.options.AllowApiKeyAuth {
			if m.options.SuccessOptional {
				c.Next()
				return
			}

			c.Abort()
			if c.GetHeader("X-API-Key") != "" {
				_ = c.Error(apperror.APIKeyAuthNotAllowed())
				return
			}
			_ = c.Error(err)
			return
		}

		// JWT auth failed, try API key auth
		userID, isAdmin, err = m.apiKeyMiddleware.Verify(c, m.options.AdminRequired)
		if err == nil {
			c.Set("userID", userID)
			c.Set("userIsAdmin", isAdmin)
			if c.IsAborted() {
				return
			}
			c.Next()
			return
		}

		if m.options.SuccessOptional {
			c.Next()
			return
		}

		// Both JWT and API key auth failed
		c.Abort()
		_ = c.Error(err)
	}
}
