package jwt

import (
	"fmt"
	"log"
	"time"

	"github.com/golang-jwt/jwt/v5"

	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/config"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/validator"
)

// Provider provides interface for JWT token generation and validation
type Provider interface {
	GenerateToken(sessionID string, duration time.Duration) (string, time.Time, error)
	GenerateTokenPair(sessionID string, accessDuration time.Duration, refreshDuration time.Duration) (accessToken string, accessExpiry time.Time, refreshToken string, refreshExpiry time.Time, err error)
	ValidateToken(tokenString string) (sessionID string, err error)
}

type provider struct {
	secret []byte
}

// NewProvider creates a new JWT provider with security validation
func NewProvider(cfg *config.Config) Provider {
	// Validate JWT secret security before creating provider
	v := validator.NewCredentialValidator()
	if err := v.ValidateJWTSecret(cfg.App.JWTSecret, cfg.App.Environment); err != nil {
		// Log detailed error with remediation steps
		log.Printf("[SECURITY ERROR] %s", err.Error())

		// In production, this is a fatal error that should prevent startup
		if cfg.App.Environment == "production" {
			panic(fmt.Sprintf("SECURITY: Invalid JWT secret in production environment: %s", err.Error()))
		}

		// In development, log warning but allow to continue
		log.Printf("[WARNING] Continuing with weak JWT secret in %s environment. This is NOT safe for production!", cfg.App.Environment)
	}

	return &provider{
		secret: []byte(cfg.App.JWTSecret),
	}
}

// GenerateToken generates a single JWT token
func (p *provider) GenerateToken(sessionID string, duration time.Duration) (string, time.Time, error) {
	expiresAt := time.Now().Add(duration)

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"session_id": sessionID,
		"exp":        expiresAt.Unix(),
	})

	tokenString, err := token.SignedString(p.secret)
	if err != nil {
		return "", time.Time{}, fmt.Errorf("failed to sign token: %w", err)
	}

	return tokenString, expiresAt, nil
}

// GenerateTokenPair generates both access token and refresh token
func (p *provider) GenerateTokenPair(sessionID string, accessDuration time.Duration, refreshDuration time.Duration) (string, time.Time, string, time.Time, error) {
	// Generate access token
	accessToken, accessExpiry, err := p.GenerateToken(sessionID, accessDuration)
	if err != nil {
		return "", time.Time{}, "", time.Time{}, fmt.Errorf("failed to generate access token: %w", err)
	}

	// Generate refresh token
	refreshToken, refreshExpiry, err := p.GenerateToken(sessionID, refreshDuration)
	if err != nil {
		return "", time.Time{}, "", time.Time{}, fmt.Errorf("failed to generate refresh token: %w", err)
	}

	return accessToken, accessExpiry, refreshToken, refreshExpiry, nil
}

// ValidateToken validates a JWT token and returns the session ID
func (p *provider) ValidateToken(tokenString string) (string, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		// Verify the signing method
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return p.secret, nil
	})

	if err != nil {
		return "", fmt.Errorf("failed to parse token: %w", err)
	}

	if !token.Valid {
		return "", fmt.Errorf("invalid token")
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return "", fmt.Errorf("invalid token claims")
	}

	sessionID, ok := claims["session_id"].(string)
	if !ok {
		return "", fmt.Errorf("session_id not found in token")
	}

	return sessionID, nil
}
