package auth

import (
	"context"
	"net/http"
	"strings"

	"git.threesix.ai/jordan/slack5-1770606136/pkg/httperror"
	"git.threesix.ai/jordan/slack5-1770606136/pkg/httpresponse"
)

// MiddlewareConfig configures the authentication middleware.
type MiddlewareConfig struct {
	// Validator is the token/key validator to use
	Validator Validator
	// TokenExtractor extracts the token from the request (optional)
	// Default: BearerTokenExtractor or APIKeyExtractor
	TokenExtractor func(*http.Request) string
	// Optional returns 401 only when a token is provided but invalid.
	// If no token is provided, the request continues without authentication.
	Optional bool
	// SkipPaths are paths that skip authentication entirely
	SkipPaths []string
}

// Middleware creates an authentication middleware.
//
// Example:
//
//	r.Use(auth.Middleware(auth.MiddlewareConfig{
//	    Validator: jwtValidator,
//	}))
//
//	// Or with optional auth (passes through if no token)
//	r.Use(auth.Middleware(auth.MiddlewareConfig{
//	    Validator: jwtValidator,
//	    Optional:  true,
//	}))
func Middleware(cfg MiddlewareConfig) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Check if path should be skipped
			for _, path := range cfg.SkipPaths {
				if strings.HasPrefix(r.URL.Path, path) {
					next.ServeHTTP(w, r)
					return
				}
			}

			// Extract token
			var token string
			if cfg.TokenExtractor != nil {
				token = cfg.TokenExtractor(r)
			} else {
				// Try Bearer token first, then API key
				token = ExtractBearerToken(r)
				if token == "" {
					token = ExtractAPIKey(r)
				}
			}

			// No token provided
			if token == "" {
				if cfg.Optional {
					next.ServeHTTP(w, r)
					return
				}
				httpresponse.Unauthorized(w, r, "authentication required")
				return
			}

			// Validate token
			user, err := cfg.Validator.Validate(r.Context(), token)
			if err != nil {
				httpresponse.Unauthorized(w, r, "invalid credentials")
				return
			}

			// Store user and token in context
			ctx := SetUser(r.Context(), user)
			ctx = SetToken(ctx, token)

			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

// RequireAuth middleware requires authentication.
// Use after auth.Middleware to ensure a user is present.
func RequireAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !IsAuthenticated(r.Context()) {
			httpresponse.Unauthorized(w, r, "authentication required")
			return
		}
		next.ServeHTTP(w, r)
	})
}

// RequireRole middleware requires the user to have a specific role.
func RequireRole(role string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			user := GetUser(r.Context())
			if user == nil {
				httpresponse.Unauthorized(w, r, "authentication required")
				return
			}
			if !user.HasRole(role) {
				httpresponse.Forbidden(w, r, "insufficient permissions")
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

// RequireAnyRole middleware requires the user to have any of the specified roles.
func RequireAnyRole(roles ...string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			user := GetUser(r.Context())
			if user == nil {
				httpresponse.Unauthorized(w, r, "authentication required")
				return
			}
			if !user.HasAnyRole(roles...) {
				httpresponse.Forbidden(w, r, "insufficient permissions")
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

// RequireScope middleware requires the user to have a specific scope.
func RequireScope(scope string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			user := GetUser(r.Context())
			if user == nil {
				httpresponse.Unauthorized(w, r, "authentication required")
				return
			}
			if !user.HasScope(scope) {
				httpresponse.Forbidden(w, r, "insufficient scope")
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

// -----------------------------------------------------------------------------
// Token Extractors
// -----------------------------------------------------------------------------

// ExtractBearerToken extracts a Bearer token from the Authorization header.
func ExtractBearerToken(r *http.Request) string {
	auth := r.Header.Get("Authorization")
	if auth == "" {
		return ""
	}

	parts := strings.SplitN(auth, " ", 2)
	if len(parts) != 2 || !strings.EqualFold(parts[0], "bearer") {
		return ""
	}

	return parts[1]
}

// ExtractAPIKey extracts an API key from the X-API-Key header.
func ExtractAPIKey(r *http.Request) string {
	return r.Header.Get("X-API-Key")
}

// ExtractFromQuery extracts a token from a query parameter.
func ExtractFromQuery(paramName string) func(*http.Request) string {
	return func(r *http.Request) string {
		return r.URL.Query().Get(paramName)
	}
}

// ExtractFromCookie extracts a token from a cookie.
func ExtractFromCookie(cookieName string) func(*http.Request) string {
	return func(r *http.Request) string {
		cookie, err := r.Cookie(cookieName)
		if err != nil {
			return ""
		}
		return cookie.Value
	}
}

// -----------------------------------------------------------------------------
// Error-returning middleware helpers (for use with app.Wrap)
// -----------------------------------------------------------------------------

// RequireAuthErr returns an error if the user is not authenticated.
// Use with app.Wrap pattern.
func RequireAuthErr(ctx context.Context) error {
	if !IsAuthenticated(ctx) {
		return httperror.Unauthorized("authentication required")
	}
	return nil
}

// RequireRoleErr returns an error if the user doesn't have the role.
// Use with app.Wrap pattern.
func RequireRoleErr(ctx context.Context, role string) error {
	user := GetUser(ctx)
	if user == nil {
		return httperror.Unauthorized("authentication required")
	}
	if !user.HasRole(role) {
		return httperror.Forbidden("insufficient permissions")
	}
	return nil
}

// RequireScopeErr returns an error if the user doesn't have the scope.
// Use with app.Wrap pattern.
func RequireScopeErr(ctx context.Context, scope string) error {
	user := GetUser(ctx)
	if user == nil {
		return httperror.Unauthorized("authentication required")
	}
	if !user.HasScope(scope) {
		return httperror.Forbidden("insufficient scope")
	}
	return nil
}
