package performance

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"testing"
	"time"

	"github.com/user-management-system/internal/auth"
	"github.com/user-management-system/internal/cache"
	"github.com/user-management-system/internal/domain"
	"github.com/user-management-system/internal/repository"
	"golang.org/x/crypto/argon2"
)

// =============================================================================
// Password Hashing Benchmarks (Argon2id)
// =============================================================================

func BenchmarkArgon2idHashing(b *testing.B) {
	password := []byte("TestPassword123!")
	salt := make([]byte, 16)
	rand.Read(salt)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = argon2.IDKey(password, salt, 5, 64*1024, 4, 32)
	}
}

func BenchmarkArgon2idHashingParallel(b *testing.B) {
	password := []byte("TestPassword123!")
	salt := make([]byte, 16)
	rand.Read(salt)

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		localSalt := make([]byte, 16)
		rand.Read(localSalt)
		for pb.Next() {
			_ = argon2.IDKey(password, localSalt, 5, 64*1024, 4, 32)
		}
	})
}

func BenchmarkArgon2idHashingDefaultParams(b *testing.B) {
	password := []byte("TestPassword123!")
	// Default params from our config: time=5, memory=64MB, threads=4
	salt := make([]byte, 16)
	rand.Read(salt)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = argon2.IDKey(password, salt, 5, 64*1024, 4, 32)
	}
	b.ReportMetric(64.0, "memory_MB")
	b.ReportMetric(5.0, "time_ops")
	b.ReportMetric(4.0, "threads")
}

// =============================================================================
// JWT Benchmarks
// =============================================================================

func BenchmarkJWTGenerateToken(b *testing.B) {
	jwtManager := auth.NewJWT("benchmark-secret-key-32bytes!", 2*time.Hour, 7*24*time.Hour)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _, _ = jwtManager.GenerateTokenPair(int64(i), "testuser")
	}
}

func BenchmarkJWTValidateToken(b *testing.B) {
	jwtManager := auth.NewJWT("benchmark-secret-key-32bytes!", 2*time.Hour, 7*24*time.Hour)
	token, _, _ := jwtManager.GenerateTokenPair(1, "testuser")

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = jwtManager.ValidateAccessToken(token)
	}
}

func BenchmarkJWTGenerateAndValidate(b *testing.B) {
	jwtManager := auth.NewJWT("benchmark-secret-key-32bytes!", 2*time.Hour, 7*24*time.Hour)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		token, _, _ := jwtManager.GenerateTokenPair(int64(i), "testuser")
		jwtManager.ValidateAccessToken(token)
	}
}

// =============================================================================
// TOTP Benchmarks
// =============================================================================

func BenchmarkTOTPGenerateSecret(b *testing.B) {
	totpManager := auth.NewTOTPManager()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = totpManager.GenerateSecret("testuser")
	}
}

func BenchmarkTOTPGenerateCurrentCode(b *testing.B) {
	totpManager := auth.NewTOTPManager()
	secret := make([]byte, 20)
	rand.Read(secret)
	_ = secret // Use the secret

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = totpManager.GenerateCurrentCode(base32StdSecret())
	}
}

func base32StdSecret() string {
	b := make([]byte, 20)
	rand.Read(b)
	return "JBSWY3DPEHPK3PXP" // Example base32 secret
}

// =============================================================================
// Recovery Code Benchmarks
// =============================================================================

func BenchmarkRecoveryCodeHashing(b *testing.B) {
	codes := generateTestCodes(10)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for _, code := range codes {
			_, _ = auth.HashRecoveryCode(code)
		}
	}
}

func BenchmarkRecoveryCodeVerification(b *testing.B) {
	codes := generateTestCodes(10)
	hashedCodes := make([]string, len(codes))
	for i, code := range codes {
		h, _ := auth.HashRecoveryCode(code)
		hashedCodes[i] = h
	}

	testCode := codes[5] // Use the 6th code

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = auth.VerifyRecoveryCode(testCode, hashedCodes)
	}
}

func generateTestCodes(count int) []string {
	codes := make([]string, count)
	for i := 0; i < count; i++ {
		b := make([]byte, RecoveryCodeLength*2)
		rand.Read(b)
		encoded := base32Encode(b)
		codes[i] = formatRecoveryCode(encoded[:10])
	}
	return codes
}

const RecoveryCodeLength = 10 // from totp.go

func base32Encode(b []byte) string {
	const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
	result := make([]byte, (len(b)*8+4)/5)
	for i := 0; i < len(result); i++ {
		var val uint32
		var bits int
		for j := 0; j < 5 && i*5+j < len(b)*8; j++ {
			if bits < 5 {
				val = (val << bits) | uint32(b[i*5/8]>>(8-bits))&0xFF
				bits += 8
			}
		}
		result[i] = alphabet[(val>>(bits-5))&0x1F]
	}
	return string(result)
}

func formatRecoveryCode(s string) string {
	if len(s) >= 10 {
		return s[:5] + "-" + s[5:10]
	}
	return s
}

// =============================================================================
// Cache Benchmarks
// =============================================================================

func BenchmarkL1CacheGet(b *testing.B) {
	l1Cache := cache.NewL1Cache()
	l1Cache.Set("test-key", "test-value", 10*time.Minute)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = l1Cache.Get("test-key")
	}
}

func BenchmarkL1CacheSet(b *testing.B) {
	l1Cache := cache.NewL1Cache()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		key := "test-key-" + hex.EncodeToString([]byte{byte(i)})
		l1Cache.Set(key, "test-value", 10*time.Minute)
	}
}

func BenchmarkL1CacheGetMiss(b *testing.B) {
	l1Cache := cache.NewL1Cache()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = l1Cache.Get("non-existent-key")
	}
}

// =============================================================================
// Database Benchmarks
// =============================================================================

func BenchmarkUserRepositoryCreate(b *testing.B) {
	db := setupBenchmarkDB(b)
	repo := repository.NewUserRepository(db)
	ctx := context.Background()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		user := &domain.User{
			Username: "benchuser" + hex.EncodeToString([]byte{byte(i)}),
			Email:    domain.StrPtr("bench@example.com"),
			Password: "hash",
			Status:   domain.UserStatusActive,
		}
		repo.Create(ctx, user)
	}
}

func BenchmarkUserRepositoryGetByID(b *testing.B) {
	db := setupBenchmarkDB(b)
	repo := repository.NewUserRepository(db)
	ctx := context.Background()

	// Pre-create user
	user := &domain.User{
		Username: "benchuser",
		Email:    domain.StrPtr("bench@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = repo.GetByID(ctx, user.ID)
	}
}

func BenchmarkUserRepositoryGetByUsername(b *testing.B) {
	db := setupBenchmarkDB(b)
	repo := repository.NewUserRepository(db)
	ctx := context.Background()

	// Pre-create user
	user := &domain.User{
		Username: "benchuser",
		Email:    domain.StrPtr("bench@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = repo.GetByUsername(ctx, "benchuser")
	}
}

func BenchmarkUserRepositoryList(b *testing.B) {
	db := setupBenchmarkDB(b)
	repo := repository.NewUserRepository(db)
	ctx := context.Background()

	// Pre-create users
	for i := 0; i < 100; i++ {
		user := &domain.User{
			Username: "benchuser" + hex.EncodeToString([]byte{byte(i)}),
			Email:    domain.StrPtr("bench@example.com"),
			Password: "hash",
			Status:   domain.UserStatusActive,
		}
		repo.Create(ctx, user)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _, _ = repo.List(ctx, 0, 100)
	}
}

func BenchmarkUserRepositoryUpdate(b *testing.B) {
	db := setupBenchmarkDB(b)
	repo := repository.NewUserRepository(db)
	ctx := context.Background()

	// Pre-create user
	user := &domain.User{
		Username: "benchuser",
		Email:    domain.StrPtr("bench@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		user.Nickname = "Updated Nickname"
		repo.Update(ctx, user)
	}
}

func BenchmarkRoleRepositoryCreate(b *testing.B) {
	db := setupBenchmarkDB(b)
	repo := repository.NewRoleRepository(db)
	ctx := context.Background()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		role := &domain.Role{
			Name: "benchrole" + hex.EncodeToString([]byte{byte(i)}),
			Code: "benchrole" + hex.EncodeToString([]byte{byte(i)}),
		}
		repo.Create(ctx, role)
	}
}

// HMAC benchmarks removed - ComputeHMAC is not exported from auth package
// ConstantTimeCompare benchmarks removed - it's internal to the auth package

// =============================================================================
// Concurrency Stress Tests
// =============================================================================

func BenchmarkConcurrentUserCreation(b *testing.B) {
	db := setupBenchmarkDB(b)
	repo := repository.NewUserRepository(db)
	ctx := context.Background()

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		i := 0
		for pb.Next() {
			user := &domain.User{
				Username: "benchuser" + hex.EncodeToString([]byte{byte(i % 256)}),
				Email:    domain.StrPtr("bench@example.com"),
				Password: "hash",
				Status:   domain.UserStatusActive,
			}
			repo.Create(ctx, user)
			i++
		}
	})
}

func BenchmarkConcurrentCacheAccess(b *testing.B) {
	l1Cache := cache.NewL1Cache()

	// Pre-populate cache
	for i := 0; i < 100; i++ {
		key := "test-key-" + hex.EncodeToString([]byte{byte(i)})
		l1Cache.Set(key, "test-value", 10*time.Minute)
	}

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		i := 0
		for pb.Next() {
			key := "test-key-" + hex.EncodeToString([]byte{byte(i % 100)})
			l1Cache.Get(key)
			i++
		}
	})
}

// =============================================================================
// Helper function - setupBenchmarkDB is defined in performance_test.go
// =============================================================================
