package services

import (
	"fmt"
	"os"
	"time"

	"veza-backend-api/internal/models"

	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
)

type JWTService struct {
	secretKey []byte
	issuer    string
	audience  string
	Config    *models.JWTConfig
}

func NewJWTService(secret, issuer, audience string) (*JWTService, error) {
	if secret == "" {
		// Fallback to env for safety during transition
		secret = os.Getenv("JWT_SECRET")
		if secret == "" {
			return nil, fmt.Errorf("JWT secret is required")
		}
	}
	if issuer == "" {
		issuer = "veza-api"
	}
	if audience == "" {
		audience = "veza-app"
	}

	// Default config
	// Action 5.1.1.4: Reduced access token expiry to 5 minutes for improved security
	config := &models.JWTConfig{
		AccessTokenTTL:  5 * time.Minute,
		RefreshTokenTTL: 30 * 24 * time.Hour,
	}

	return &JWTService{
		secretKey: []byte(secret),
		issuer:    issuer,
		audience:  audience,
		Config:    config,
	}, nil
}

func (s *JWTService) GetConfig() *models.JWTConfig {
	return s.Config
}

func (s *JWTService) GenerateAccessToken(user *models.User) (string, error) {
	claims := models.CustomClaims{
		UserID:       user.ID,
		Email:        user.Email,
		Username:     user.Username,
		Role:         user.Role,
		TokenVersion: user.TokenVersion,
		TokenType:    "access",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.Config.AccessTokenTTL)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    s.issuer,
			Audience:  jwt.ClaimStrings{s.audience},
			ID:        uuid.NewString(),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(s.secretKey)
}

func (s *JWTService) GenerateRefreshToken(user *models.User) (string, error) {
	claims := models.CustomClaims{
		UserID:       user.ID,
		TokenVersion: user.TokenVersion,
		IsRefresh:    true, // Mark as refresh token
		TokenType:    "refresh",
		TokenFamily:  uuid.NewString(), // Nouvelle famille de token
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.Config.RefreshTokenTTL)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    s.issuer,
			Audience:  jwt.ClaimStrings{s.audience},
			ID:        uuid.NewString(),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(s.secretKey)
}

// GenerateTokenPair génère une paire de tokens (access + refresh) en une seule opération
func (s *JWTService) GenerateTokenPair(user *models.User) (*models.TokenPair, error) {
	// Generate access token
	accessToken, err := s.GenerateAccessToken(user)
	if err != nil {
		return nil, fmt.Errorf("failed to generate access token: %w", err)
	}

	// Generate refresh token
	refreshToken, err := s.GenerateRefreshToken(user)
	if err != nil {
		return nil, fmt.Errorf("failed to generate refresh token: %w", err)
	}

	return &models.TokenPair{
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
		ExpiresIn:    int(s.Config.AccessTokenTTL.Seconds()),
	}, nil
}

// VerifyToken valide et parse un token JWT
func (s *JWTService) VerifyToken(tokenString string) (*models.CustomClaims, error) {
	return s.ValidateToken(tokenString)
}

// ValidateToken valide un token JWT et retourne les claims
func (s *JWTService) ValidateToken(tokenString string) (*models.CustomClaims, error) {
	// Parse avec validation des claims standards (exp, iat, nbf) ET custom (iss, aud)
	token, err := jwt.ParseWithClaims(tokenString, &models.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		// Validation stricte de l'algorithme (MOD-P2-002)
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		if token.Method.Alg() != "HS256" {
			return nil, fmt.Errorf("invalid signing algorithm: %v, expected HS256", token.Method.Alg())
		}
		return s.secretKey, nil
	},
		// Options de validation stricte
		jwt.WithIssuer(s.issuer),
		jwt.WithAudience(s.audience),
		jwt.WithExpirationRequired(),
	)

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

	if claims, ok := token.Claims.(*models.CustomClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, fmt.Errorf("invalid token")
}

// ParseToken parse un token JWT sans validation complète (utilise ValidateToken)
func (s *JWTService) ParseToken(tokenString string) (*models.CustomClaims, error) {
	return s.ValidateToken(tokenString)
}

// ExtractClaims extrait les claims d'un token JWT
func (s *JWTService) ExtractClaims(tokenString string) (*models.CustomClaims, error) {
	return s.ValidateToken(tokenString)
}

// ExtractUserID extrait l'ID utilisateur depuis un token JWT
// MIGRATION UUID: retourne uuid.UUID au lieu de int64
func (s *JWTService) ExtractUserID(tokenString string) (uuid.UUID, error) {
	claims, err := s.ValidateToken(tokenString)
	if err != nil {
		return uuid.Nil, fmt.Errorf("failed to extract user ID: %w", err)
	}
	return claims.UserID, nil
}

// VerifyTokenVersion vérifie si la version du token correspond à celle de l'utilisateur
func (s *JWTService) VerifyTokenVersion(claims *models.CustomClaims, userTokenVersion int) error {
	if claims.TokenVersion != userTokenVersion {
		return fmt.Errorf("token version mismatch: token version %d does not match user version %d", claims.TokenVersion, userTokenVersion)
	}
	return nil
}
