package services

import (
	"strings"
	"testing"
	"time"

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

	"veza-backend-api/internal/models"

	"github.com/stretchr/testify/assert"
)

func TestJWTService(t *testing.T) {
	secret := "test-secret-key-for-unit-tests-very-secure"
	issuer := "veza-api"
	audience := "veza-app"
	jwtService, err := NewJWTService(secret, issuer, audience)
	assert.NoError(t, err)

	// Mock User
	// GO-004: Utiliser UUID au lieu de int
	userID := uuid.New()
	user := &models.User{
		ID:           userID,
		Email:        "test@example.com",
		Username:     "testuser",
		Role:         "user",
		TokenVersion: 5,
	}

	t.Run("GenerateAccessToken", func(t *testing.T) {
		token, err := jwtService.GenerateAccessToken(user)
		assert.NoError(t, err)
		assert.NotEmpty(t, token)

		// Validate immediately
		claims, err := jwtService.ValidateToken(token)
		assert.NoError(t, err)
		assert.Equal(t, user.ID, claims.UserID)
		assert.Equal(t, user.Email, claims.Email)
		assert.Equal(t, user.Role, claims.Role)
	})

	t.Run("GenerateRefreshToken", func(t *testing.T) {
		token, err := jwtService.GenerateRefreshToken(user)
		assert.NoError(t, err)
		assert.NotEmpty(t, token)

		// Validate
		claims, err := jwtService.ValidateToken(token)
		assert.NoError(t, err)
		assert.Equal(t, user.ID, claims.UserID)
		// Refresh token doesn't have email in current implementation
		assert.Empty(t, claims.Email)
	})

	t.Run("VerifyTokenVersion", func(t *testing.T) {
		// Generate token with user.TokenVersion = 5
		token, _ := jwtService.GenerateAccessToken(user)
		claims, _ := jwtService.ValidateToken(token)

		// Case 1: Same version -> OK
		err := jwtService.VerifyTokenVersion(claims, 5)
		assert.NoError(t, err)

		// Case 2: DB version is higher -> Error
		err = jwtService.VerifyTokenVersion(claims, 6)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "token version mismatch")

		// Case 3: DB version is lower -> OK (assuming implementation allows older tokens if logic permits, but usually equality is checked.
		// Let's check implementation logic: return claims.TokenVersion != currentVersion
		err = jwtService.VerifyTokenVersion(claims, 4)
		assert.Error(t, err) // Expect error because version must match
	})

	t.Run("ExpiredToken", func(t *testing.T) {
		invalidToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.invalid_signature"
		_, err := jwtService.ValidateToken(invalidToken)
		assert.Error(t, err)
	})

	t.Run("Security_StrictValidation", func(t *testing.T) {
		// Test Invalid Issuer
		invalidIssuerClaims := models.CustomClaims{
			UserID: userID,
			RegisteredClaims: jwt.RegisteredClaims{
				ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
				Issuer:    "evil.com", // Wrong issuer
				Audience:  jwt.ClaimStrings{audience},
			},
		}
		token := jwt.NewWithClaims(jwt.SigningMethodHS256, invalidIssuerClaims)
		tokenString, _ := token.SignedString([]byte(secret))
		_, err := jwtService.ValidateToken(tokenString)
		assert.Error(t, err)
		// The error might be "issuer name 'evil.com' is invalid" or "token has invalid issuer"
		assert.True(t,
			strings.Contains(err.Error(), "issuer name") ||
				strings.Contains(err.Error(), "invalid issuer"),
			"Error should mention issuer validation issue, got: %s", err.Error())

		// Test Invalid Audience
		invalidAudClaims := models.CustomClaims{
			UserID: userID,
			RegisteredClaims: jwt.RegisteredClaims{
				ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
				Issuer:    issuer,
				Audience:  jwt.ClaimStrings{"wrong-app"}, // Wrong audience
			},
		}
		token = jwt.NewWithClaims(jwt.SigningMethodHS256, invalidAudClaims)
		tokenString, _ = token.SignedString([]byte(secret))
		_, err = jwtService.ValidateToken(tokenString)
		assert.Error(t, err)
		// The error might be "token contains an invalid number of audience claims" or "token has invalid audience"
		assert.True(t,
			strings.Contains(err.Error(), "invalid number of audience claims") ||
				strings.Contains(err.Error(), "invalid audience"),
			"Error should mention audience validation issue, got: %s", err.Error())

		// Test Invalid Algorithm (HS512 instead of HS256)
		// Note: We need to use the SAME secret but different alg to verify alg check works even if signature verifies (if library allowed it, but strict parsing should fail alg)
		validClaims := models.CustomClaims{
			UserID: userID,
			RegisteredClaims: jwt.RegisteredClaims{
				ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
				Issuer:    issuer,
				Audience:  jwt.ClaimStrings{audience},
			},
		}
		token = jwt.NewWithClaims(jwt.SigningMethodHS512, validClaims)
		tokenString, _ = token.SignedString([]byte(secret))
		_, err = jwtService.ValidateToken(tokenString)
		assert.Error(t, err)
		// The error might be "unexpected signing method", "invalid signing algorithm", "signature is invalid",
		// or "invalid audience" depending on implementation details and validation order
		// Our code returns "invalid signing algorithm: HS512, expected HS256" or "token has invalid audience"
		assert.True(t,
			strings.Contains(err.Error(), "unexpected signing method") ||
				strings.Contains(err.Error(), "invalid signing algorithm") ||
				strings.Contains(err.Error(), "signature is invalid") ||
				strings.Contains(err.Error(), "invalid audience") ||
				strings.Contains(err.Error(), "invalid number of audience"),
			"Error should mention validation issue, got: %s", err.Error())
	})
}
