package utils

import (
	"strings"
	"testing"
	"time"
)

func resetTokenState(t *testing.T) {
	t.Helper()
	ResetTokenStateForTests()
	t.Cleanup(ResetTokenStateForTests)
}

func TestTokenBlacklist(t *testing.T) {
	resetTokenState(t)

	blacklist := NewInMemoryTokenBlacklist()

	// Test Add and IsBlacklisted
	tokenHash := "test-token-hash"
	expiry := time.Now().Add(1 * time.Hour)

	err := blacklist.Add(tokenHash, expiry)
	if err != nil {
		t.Errorf("Add() error = %v", err)
	}

	if !blacklist.IsBlacklisted(tokenHash) {
		t.Error("IsBlacklisted() should return true for added token")
	}

	// Test Size
	if size := blacklist.Size(); size != 1 {
		t.Errorf("Size() = %v, want 1", size)
	}

	// Test non-existent token
	if blacklist.IsBlacklisted("non-existent") {
		t.Error("IsBlacklisted() should return false for non-existent token")
	}
}

func TestTokenBlacklistExpiry(t *testing.T) {
	resetTokenState(t)

	blacklist := NewInMemoryTokenBlacklist()

	// Add expired token
	tokenHash := "expired-token"
	expiry := time.Now().Add(-1 * time.Hour) // Already expired

	blacklist.Add(tokenHash, expiry)

	// Should return false for expired token
	if blacklist.IsBlacklisted(tokenHash) {
		t.Error("IsBlacklisted() should return false for expired token")
	}

	// Size should be 0 after checking expired token
	if size := blacklist.Size(); size != 0 {
		t.Errorf("Size() = %v, want 0 after expired token removal", size)
	}
}

func TestTokenBlacklistCleanup(t *testing.T) {
	resetTokenState(t)

	blacklist := NewInMemoryTokenBlacklist()

	// Add multiple tokens with different expiry times
	blacklist.Add("token1", time.Now().Add(1*time.Hour))  // Valid
	blacklist.Add("token2", time.Now().Add(-1*time.Hour)) // Expired
	blacklist.Add("token3", time.Now().Add(2*time.Hour))  // Valid

	// Initial size should be 3
	if size := blacklist.Size(); size != 3 {
		t.Errorf("Initial size = %v, want 3", size)
	}

	// Run cleanup
	err := blacklist.Cleanup()
	if err != nil {
		t.Errorf("Cleanup() error = %v", err)
	}

	// Size should be 2 after cleanup (expired token removed)
	if size := blacklist.Size(); size != 2 {
		t.Errorf("Size after cleanup = %v, want 2", size)
	}
}

func TestGenerateToken(t *testing.T) {
	resetTokenState(t)

	secret := "test-secret"
	userID := uint(123)
	role := "admin"
	branchID := uint(1)

	accessToken, refreshToken, err := GenerateToken(userID, "testuser", role, branchID, secret)

	if err != nil {
		t.Errorf("GenerateToken() error = %v", err)
	}

	if accessToken == "" {
		t.Error("GenerateToken() accessToken is empty")
	}

	if refreshToken == "" {
		t.Error("GenerateToken() refreshToken is empty")
	}

	// Validate the generated access token
	claims, err := ValidateToken(accessToken, secret)
	if err != nil {
		t.Errorf("ValidateToken() error = %v", err)
	}

	if claims.UserID != userID {
		t.Errorf("Token UserID = %v, want %v", claims.UserID, userID)
	}

	if claims.Role != role {
		t.Errorf("Token Role = %v, want %v", claims.Role, role)
	}

	if claims.BranchID != branchID {
		t.Errorf("Token BranchID = %v, want %v", claims.BranchID, branchID)
	}
}

func TestRevokeToken(t *testing.T) {
	resetTokenState(t)

	secret := "test-secret"
	accessToken, _, err := GenerateToken(123, "testuser", "admin", 1, secret)
	if err != nil {
		t.Fatalf("GenerateToken() error = %v", err)
	}

	// Token should be valid initially
	_, err = ValidateToken(accessToken, secret)
	if err != nil {
		t.Errorf("ValidateToken() before revoke error = %v", err)
	}

	// Revoke the token
	err = RevokeToken(accessToken, secret)
	if err != nil {
		t.Errorf("RevokeToken() error = %v", err)
	}

	// Token should be invalid after revocation
	_, err = ValidateToken(accessToken, secret)
	if err == nil {
		t.Error("ValidateToken() should fail for revoked token")
	}

	if err.Error() != "token has been revoked" {
		t.Errorf("ValidateToken() error = %v, want 'token has been revoked'", err)
	}
}

func TestRevokeAllUserTokens(t *testing.T) {
	resetTokenState(t)

	secret := "test-secret"
	accessToken, _, err := GenerateToken(456, "testuser", "admin", 1, secret)
	if err != nil {
		t.Fatalf("GenerateToken() error = %v", err)
	}

	if _, err := ValidateToken(accessToken, secret); err != nil {
		t.Fatalf("ValidateToken() before revoke error = %v", err)
	}

	if err := RevokeAllUserTokens(456); err != nil {
		t.Fatalf("RevokeAllUserTokens() error = %v", err)
	}

	if _, err := ValidateToken(accessToken, secret); err == nil {
		t.Fatal("ValidateToken() should fail for user-revoked token")
	}
}

func TestHashToken(t *testing.T) {
	resetTokenState(t)

	token := "test-token"
	hash1 := HashToken(token)
	hash2 := HashToken(token)

	// Same token should produce same hash
	if hash1 != hash2 {
		t.Error("HashToken() should produce consistent hashes")
	}

	// Different tokens should produce different hashes
	hash3 := HashToken("different-token")
	if hash1 == hash3 {
		t.Error("HashToken() should produce different hashes for different tokens")
	}

	// Hash should be non-empty
	if hash1 == "" {
		t.Error("HashToken() should not return empty string")
	}
}

func TestGetBlacklistStats(t *testing.T) {
	resetTokenState(t)

	// Create a new blacklist for this test to avoid interference
	oldBlacklist := GetTokenBlacklist()
	defer SetTokenBlacklist(oldBlacklist)

	// Set a fresh blacklist
	SetTokenBlacklist(NewInMemoryTokenBlacklist())

	// Add some tokens to blacklist
	blacklist := GetTokenBlacklist().(*InMemoryTokenBlacklist)
	blacklist.Add("token1", time.Now().Add(1*time.Hour))
	blacklist.Add("token2", time.Now().Add(1*time.Hour))

	stats := GetBlacklistStats()

	if stats["size"] != 2 {
		t.Errorf("Stats size = %v, want 2", stats["size"])
	}

	typeName, ok := stats["type"].(string)
	if !ok || !strings.Contains(typeName, "InMemoryTokenBlacklist") {
		t.Errorf("Stats type = %v, want contains 'InMemoryTokenBlacklist'", stats["type"])
	}
}

func BenchmarkGenerateToken(b *testing.B) {
	secret := "test-secret"

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		GenerateToken(123, "testuser", "admin", 1, secret)
	}
}

func BenchmarkValidateToken(b *testing.B) {
	secret := "test-secret"
	token, _, _ := GenerateToken(123, "testuser", "admin", 1, secret)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		ValidateToken(token, secret)
	}
}

func BenchmarkHashToken(b *testing.B) {
	token := "test-token-for-benchmarking"

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		HashToken(token)
	}
}

func BenchmarkBlacklistOperations(b *testing.B) {
	blacklist := NewInMemoryTokenBlacklist()
	tokenHash := "benchmark-token"
	expiry := time.Now().Add(1 * time.Hour)

	b.Run("Add", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			blacklist.Add(tokenHash, expiry)
		}
	})

	b.Run("IsBlacklisted", func(b *testing.B) {
		blacklist.Add(tokenHash, expiry)
		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			blacklist.IsBlacklisted(tokenHash)
		}
	})
}
