package auth_test

import (
	"fmt"
	"os"
	"testing"

	"github.com/rede5/gohorsejobs/backend/internal/infrastructure/auth"
	"github.com/stretchr/testify/assert"
)

func TestJWTService_HashAndVerifyPassword(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_HashAndVerifyPassword ===")

	// Setup
	os.Setenv("PASSWORD_PEPPER", "test-pepper")
	defer os.Unsetenv("PASSWORD_PEPPER")

	service := auth.NewJWTService("secret", "issuer")

	t.Run("Should hash and verify password correctly", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing password hash and verify")
		password := "mysecurepassword"
		hash, err := service.HashPassword(password)
		fmt.Printf("[TEST LOG] Hash generated: %s...\n", hash[:20])
		assert.NoError(t, err)
		assert.NotEmpty(t, hash)

		valid := service.VerifyPassword(hash, password)
		fmt.Printf("[TEST LOG] Password verification result: %v\n", valid)
		assert.True(t, valid)
	})

	t.Run("Should fail verification with wrong password", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing wrong password rejection")
		password := "password"
		hash, _ := service.HashPassword(password)

		valid := service.VerifyPassword(hash, "wrong-password")
		fmt.Printf("[TEST LOG] Wrong password verification result: %v (expected false)\n", valid)
		assert.False(t, valid)
	})

	t.Run("Should fail verification with wrong pepper", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing wrong pepper rejection")
		password := "password"
		hash, _ := service.HashPassword(password)

		// Change pepper
		os.Setenv("PASSWORD_PEPPER", "wrong-pepper")
		valid := service.VerifyPassword(hash, password)
		fmt.Printf("[TEST LOG] Wrong pepper verification result: %v (expected false)\n", valid)
		assert.False(t, valid)

		// Reset pepper
		os.Setenv("PASSWORD_PEPPER", "test-pepper")
	})
}

func TestJWTService_HashPassword_NoPepper(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_HashPassword_NoPepper ===")

	os.Unsetenv("PASSWORD_PEPPER")
	defer os.Setenv("PASSWORD_PEPPER", "test-pepper")

	service := auth.NewJWTService("secret", "issuer")

	password := "password-no-pepper"
	hash, err := service.HashPassword(password)
	fmt.Printf("[TEST LOG] Hash without pepper: %s...\n", hash[:20])
	assert.NoError(t, err)
	assert.NotEmpty(t, hash)

	// Should still verify (empty pepper is still valid)
	valid := service.VerifyPassword(hash, password)
	fmt.Printf("[TEST LOG] Verification without pepper: %v\n", valid)
	assert.True(t, valid)
}

func TestJWTService_TokenOperations(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_TokenOperations ===")

	service := auth.NewJWTService("secret", "issuer")

	t.Run("Should generate and validate token", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing token generation and validation")
		userID := "user-123"
		tenantID := "tenant-456"
		roles := []string{"admin"}

		token, err := service.GenerateToken(userID, tenantID, roles)
		fmt.Printf("[TEST LOG] Token generated: %s...\n", token[:50])
		assert.NoError(t, err)
		assert.NotEmpty(t, token)

		claims, err := service.ValidateToken(token)
		fmt.Printf("[TEST LOG] Claims: sub=%v, tenant=%v\n", claims["sub"], claims["tenant"])
		assert.NoError(t, err)
		assert.Equal(t, userID, claims["sub"])
		assert.Equal(t, tenantID, claims["tenant"])
	})

	t.Run("Should fail invalid token", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing invalid token rejection")
		claims, err := service.ValidateToken("invalid-token")
		fmt.Printf("[TEST LOG] Invalid token error: %v\n", err)
		assert.Error(t, err)
		assert.Nil(t, claims)
	})
}

func TestJWTService_GenerateToken_ExpirationParsing(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_GenerateToken_ExpirationParsing ===")

	service := auth.NewJWTService("secret", "issuer")

	t.Run("Default expiration (no env)", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing default expiration (24h)")
		os.Unsetenv("JWT_EXPIRATION")

		token, err := service.GenerateToken("user", "tenant", []string{"role"})
		fmt.Printf("[TEST LOG] Token with default expiration: %s...\n", token[:50])
		assert.NoError(t, err)
		assert.NotEmpty(t, token)

		claims, _ := service.ValidateToken(token)
		fmt.Printf("[TEST LOG] Token claims: exp=%v\n", claims["exp"])
		assert.NotNil(t, claims["exp"])
	})

	t.Run("Days format (7d)", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing days format expiration (7d)")
		os.Setenv("JWT_EXPIRATION", "7d")
		defer os.Unsetenv("JWT_EXPIRATION")

		token, err := service.GenerateToken("user", "tenant", []string{"role"})
		fmt.Printf("[TEST LOG] Token with 7d expiration: %s...\n", token[:50])
		assert.NoError(t, err)
		assert.NotEmpty(t, token)
	})

	t.Run("Duration format (2h)", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing duration format expiration (2h)")
		os.Setenv("JWT_EXPIRATION", "2h")
		defer os.Unsetenv("JWT_EXPIRATION")

		token, err := service.GenerateToken("user", "tenant", []string{"role"})
		fmt.Printf("[TEST LOG] Token with 2h expiration: %s...\n", token[:50])
		assert.NoError(t, err)
		assert.NotEmpty(t, token)
	})

	t.Run("Invalid days format fallback", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing invalid days format (abcd)")
		os.Setenv("JWT_EXPIRATION", "abcd")
		defer os.Unsetenv("JWT_EXPIRATION")

		token, err := service.GenerateToken("user", "tenant", []string{"role"})
		fmt.Printf("[TEST LOG] Token with invalid format (fallback): %s...\n", token[:50])
		assert.NoError(t, err)
		assert.NotEmpty(t, token)
	})

	t.Run("Invalid day number fallback", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing invalid day number (xxd)")
		os.Setenv("JWT_EXPIRATION", "xxd")
		defer os.Unsetenv("JWT_EXPIRATION")

		token, err := service.GenerateToken("user", "tenant", []string{"role"})
		fmt.Printf("[TEST LOG] Token with xxd format (fallback): %s...\n", token[:50])
		assert.NoError(t, err)
		assert.NotEmpty(t, token)
	})
}

func TestJWTService_ValidateToken_WrongSigningMethod(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_ValidateToken_WrongSigningMethod ===")

	service := auth.NewJWTService("secret", "issuer")

	// A token signed with a different algorithm would fail validation
	// This is hard to test directly, but we can test with a malformed token
	t.Run("Malformed token", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing malformed token")
		claims, err := service.ValidateToken("eyJhbGciOiJub25lIn0.eyJzdWIiOiIxMjM0NTY3ODkwIn0.")
		fmt.Printf("[TEST LOG] Malformed token error: %v\n", err)
		assert.Error(t, err)
		assert.Nil(t, claims)
	})

	t.Run("Token with different secret", func(t *testing.T) {
		fmt.Println("[TEST LOG] Testing token from different secret")
		otherService := auth.NewJWTService("different-secret", "issuer")
		token, _ := otherService.GenerateToken("user", "tenant", []string{"role"})

		claims, err := service.ValidateToken(token)
		fmt.Printf("[TEST LOG] Wrong secret error: %v\n", err)
		assert.Error(t, err)
		assert.Nil(t, claims)
	})
}

func TestJWTService_NewJWTService(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_NewJWTService ===")

	service := auth.NewJWTService("my-secret", "my-issuer")
	fmt.Printf("[TEST LOG] Service created: %v\n", service)
	assert.NotNil(t, service)
}

// ============ ADDITIONAL TESTS (10% expansion) ============

func TestJWTService_HashPassword_EmptyPassword(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_HashPassword_EmptyPassword ===")

	os.Setenv("PASSWORD_PEPPER", "test-pepper")
	defer os.Unsetenv("PASSWORD_PEPPER")

	service := auth.NewJWTService("secret", "issuer")

	t.Run("Empty password should still hash", func(t *testing.T) {
		hash, err := service.HashPassword("")
		fmt.Printf("[TEST LOG] Empty password hash: %s...\n", hash[:20])
		assert.NoError(t, err)
		assert.NotEmpty(t, hash)

		// Should verify with empty password
		valid := service.VerifyPassword(hash, "")
		assert.True(t, valid)
	})
}

func TestJWTService_PepperConsistency(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_PepperConsistency ===")

	t.Run("Same pepper produces verifiable hash", func(t *testing.T) {
		os.Setenv("PASSWORD_PEPPER", "consistent-pepper")
		service := auth.NewJWTService("secret", "issuer")

		password := "Admin@2025!"
		hash1, _ := service.HashPassword(password)

		// Verify with same pepper
		valid := service.VerifyPassword(hash1, password)
		assert.True(t, valid, "Password should verify with same pepper")
		os.Unsetenv("PASSWORD_PEPPER")
	})

	t.Run("Different peppers produce different hashes", func(t *testing.T) {
		service := auth.NewJWTService("secret", "issuer")

		os.Setenv("PASSWORD_PEPPER", "pepper-1")
		hash1, _ := service.HashPassword("password")

		os.Setenv("PASSWORD_PEPPER", "pepper-2")
		hash2, _ := service.HashPassword("password")

		// Hashes should be different
		assert.NotEqual(t, hash1, hash2, "Different peppers should produce different hashes")
		os.Unsetenv("PASSWORD_PEPPER")
	})
}

func TestJWTService_TokenClaims_Content(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_TokenClaims_Content ===")

	service := auth.NewJWTService("secret", "issuer")

	t.Run("Token should contain all expected claims", func(t *testing.T) {
		userID := "019b51a9-385f-7416-be8c-9960727531a3"
		tenantID := "tenant-xyz"
		roles := []string{"admin", "superadmin"}

		token, err := service.GenerateToken(userID, tenantID, roles)
		assert.NoError(t, err)

		claims, err := service.ValidateToken(token)
		assert.NoError(t, err)

		// Check all claims
		assert.Equal(t, userID, claims["sub"])
		assert.Equal(t, tenantID, claims["tenant"])
		assert.Equal(t, "issuer", claims["iss"])
		assert.NotNil(t, claims["exp"])
		assert.NotNil(t, claims["iat"])

		// Check roles
		rolesFromClaims := claims["roles"].([]interface{})
		assert.Len(t, rolesFromClaims, 2)
	})
}

func TestJWTService_LongPassword(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_LongPassword ===")

	os.Setenv("PASSWORD_PEPPER", "test-pepper")
	defer os.Unsetenv("PASSWORD_PEPPER")

	service := auth.NewJWTService("secret", "issuer")

	t.Run("Very long password should work", func(t *testing.T) {
		// bcrypt has a 72 byte limit, test behavior
		longPassword := "ShortPasswordButLongEnoughForTest"
		hash, err := service.HashPassword(longPassword)
		assert.NoError(t, err)
		assert.NotEmpty(t, hash)

		valid := service.VerifyPassword(hash, longPassword)
		assert.True(t, valid)
	})
}

func TestJWTService_SpecialCharactersPassword(t *testing.T) {
	fmt.Println("\n[TEST] === TestJWTService_SpecialCharactersPassword ===")

	os.Setenv("PASSWORD_PEPPER", "test-pepper")
	defer os.Unsetenv("PASSWORD_PEPPER")

	service := auth.NewJWTService("secret", "issuer")

	specialPasswords := []string{
		"Admin@2025!",
		"Pässwörd123",
		"密码123",
		"パスワード",
		"🔐SecurePass!",
		"test\nwith\nnewlines",
		"tab\there",
	}

	for _, password := range specialPasswords {
		t.Run("Password: "+password[:min(10, len(password))], func(t *testing.T) {
			hash, err := service.HashPassword(password)
			assert.NoError(t, err)
			assert.NotEmpty(t, hash)

			valid := service.VerifyPassword(hash, password)
			assert.True(t, valid, "Should verify password with special chars")
		})
	}
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
