package service_test

import (
	"context"
	"strings"
	"testing"

	"github.com/user-management-system/internal/cache"
	"github.com/user-management-system/internal/service"
)

// =============================================================================
// Captcha Service Tests - Phase 1
// =============================================================================

func TestCaptchaService_Generate(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
	svc := service.NewCaptchaService(cacheManager)
	ctx := context.Background()

	t.Run("Generate captcha", func(t *testing.T) {
		result, err := svc.Generate(ctx)
		if err != nil {
			t.Fatalf("Generate failed: %v", err)
		}
		if result.CaptchaID == "" {
			t.Error("Expected captcha ID")
		}
		if len(result.ImageData) == 0 {
			t.Error("Expected image data")
		}
	})
}

func TestCaptchaService_Verify(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
	svc := service.NewCaptchaService(cacheManager)
	ctx := context.Background()

	t.Run("Verify captcha success", func(t *testing.T) {
		result, _ := svc.Generate(ctx)
		// Get the answer from cache
		val, ok := cacheManager.Get(ctx, "captcha:"+result.CaptchaID)
		if !ok {
			t.Fatal("Captcha not found in cache")
		}
		answer := val.(string)

		valid := svc.Verify(ctx, result.CaptchaID, answer)
		if !valid {
			t.Error("Expected captcha to be valid")
		}
	})

	t.Run("Verify captcha with wrong answer", func(t *testing.T) {
		result, _ := svc.Generate(ctx)
		valid := svc.Verify(ctx, result.CaptchaID, "wrong")
		if valid {
			t.Error("Expected captcha to be invalid")
		}
	})

	t.Run("Verify captcha with empty ID", func(t *testing.T) {
		valid := svc.Verify(ctx, "", "answer")
		if valid {
			t.Error("Expected false for empty ID")
		}
	})

	t.Run("Verify captcha with empty answer", func(t *testing.T) {
		result, _ := svc.Generate(ctx)
		valid := svc.Verify(ctx, result.CaptchaID, "")
		if valid {
			t.Error("Expected false for empty answer")
		}
	})

	t.Run("Verify captcha twice (one-time use)", func(t *testing.T) {
		result, _ := svc.Generate(ctx)
		val, _ := cacheManager.Get(ctx, "captcha:"+result.CaptchaID)
		answer := val.(string)

		// First verify
		svc.Verify(ctx, result.CaptchaID, answer)
		// Second verify should fail
		valid := svc.Verify(ctx, result.CaptchaID, answer)
		if valid {
			t.Error("Expected captcha to be invalid after first use")
		}
	})

	t.Run("Verify captcha case insensitive", func(t *testing.T) {
		result, _ := svc.Generate(ctx)
		val, _ := cacheManager.Get(ctx, "captcha:"+result.CaptchaID)
		answer := val.(string)

		// Verify with uppercase
		valid := svc.Verify(ctx, result.CaptchaID, strings.ToUpper(answer))
		if !valid {
			t.Error("Expected case-insensitive verification")
		}
	})
}

func TestCaptchaService_ValidateCaptcha(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
	svc := service.NewCaptchaService(cacheManager)
	ctx := context.Background()

	t.Run("ValidateCaptcha with empty ID", func(t *testing.T) {
		err := svc.ValidateCaptcha(ctx, "", "answer")
		if err == nil {
			t.Error("Expected error for empty ID")
		}
	})

	t.Run("ValidateCaptcha with empty answer", func(t *testing.T) {
		err := svc.ValidateCaptcha(ctx, "id", "")
		if err == nil {
			t.Error("Expected error for empty answer")
		}
	})
}

func TestCaptchaService_VerifyWithoutDelete(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
	svc := service.NewCaptchaService(cacheManager)
	ctx := context.Background()

	t.Run("VerifyWithoutDelete success", func(t *testing.T) {
		result, _ := svc.Generate(ctx)
		val, _ := cacheManager.Get(ctx, "captcha:"+result.CaptchaID)
		answer := val.(string)

		valid := svc.VerifyWithoutDelete(ctx, result.CaptchaID, answer)
		if !valid {
			t.Error("Expected captcha to be valid")
		}

		// Should still be valid after VerifyWithoutDelete
		valid2 := svc.VerifyWithoutDelete(ctx, result.CaptchaID, answer)
		if !valid2 {
			t.Error("Expected captcha to still be valid after VerifyWithoutDelete")
		}
	})

	t.Run("VerifyWithoutDelete with wrong answer", func(t *testing.T) {
		result, _ := svc.Generate(ctx)
		valid := svc.VerifyWithoutDelete(ctx, result.CaptchaID, "wrong")
		if valid {
			t.Error("Expected captcha to be invalid")
		}
	})

	t.Run("VerifyWithoutDelete with empty ID", func(t *testing.T) {
		valid := svc.VerifyWithoutDelete(ctx, "", "answer")
		if valid {
			t.Error("Expected false for empty ID")
		}
	})
}

// =============================================================================
// Settings Service Tests - Phase 1
// =============================================================================

func TestSettingsService_GetSettings(t *testing.T) {
	svc := service.NewSettingsService()
	ctx := context.Background()

	t.Run("GetSettings returns default values", func(t *testing.T) {
		settings, err := svc.GetSettings(ctx)
		if err != nil {
			t.Fatalf("GetSettings failed: %v", err)
		}
		if settings.System.Name == "" {
			t.Error("Expected system name")
		}
		if settings.System.Version == "" {
			t.Error("Expected system version")
		}
	})
}

// =============================================================================
// Email Code Service Tests - Phase 1
// =============================================================================

func TestEmailCodeService_New(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

	t.Run("NewEmailCodeService", func(t *testing.T) {
		cfg := service.DefaultEmailCodeConfig()
		svc := service.NewEmailCodeService(nil, cacheManager, cfg)
		if svc == nil {
			t.Error("Expected service instance")
		}
	})
}

// =============================================================================
// SMS Code Service Tests - Phase 1
// =============================================================================

func TestSMSCodeService_New(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

	t.Run("NewSMSCodeService", func(t *testing.T) {
		cfg := service.DefaultSMSCodeConfig()
		svc := service.NewSMSCodeService(nil, cacheManager, cfg)
		if svc == nil {
			t.Error("Expected service instance")
		}
	})
}

// =============================================================================
// Password Reset Service Tests - Phase 1
// =============================================================================

func TestPasswordResetService_New(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

	t.Run("NewPasswordResetService", func(t *testing.T) {
		cfg := service.DefaultPasswordResetConfig()
		svc := service.NewPasswordResetService(nil, cacheManager, cfg)
		if svc == nil {
			t.Error("Expected service instance")
		}
	})
}

// =============================================================================
// Email Code Service Tests - Extended
// =============================================================================

func TestEmailCodeService_SendEmailCode(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
	provider := &service.MockEmailProvider{}
	cfg := service.DefaultEmailCodeConfig()
	svc := service.NewEmailCodeService(provider, cacheManager, cfg)
	ctx := context.Background()

	t.Run("Send email code success", func(t *testing.T) {
		err := svc.SendEmailCode(ctx, "test@example.com", "login")
		if err != nil {
			t.Fatalf("SendEmailCode failed: %v", err)
		}
	})

	t.Run("Send email code with cooldown", func(t *testing.T) {
		// First request
		svc.SendEmailCode(ctx, "cooldown@example.com", "login")
		// Second request should hit cooldown
		err := svc.SendEmailCode(ctx, "cooldown@example.com", "login")
		if err == nil {
			t.Error("Expected rate limit error due to cooldown")
		}
	})
}

func TestEmailCodeService_VerifyEmailCode(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
	provider := &service.MockEmailProvider{}
	cfg := service.DefaultEmailCodeConfig()
	svc := service.NewEmailCodeService(provider, cacheManager, cfg)
	ctx := context.Background()

	t.Run("Verify email code success", func(t *testing.T) {
		email := "verify@example.com"
		svc.SendEmailCode(ctx, email, "login")
		// Get the code from cache
		val, ok := cacheManager.Get(ctx, "email_code:login:"+email)
		if !ok {
			t.Fatal("Code not found in cache")
		}
		code := val.(string)
		err := svc.VerifyEmailCode(ctx, email, "login", code)
		if err != nil {
			t.Fatalf("VerifyEmailCode failed: %v", err)
		}
	})

	t.Run("Verify email code with wrong code", func(t *testing.T) {
		email := "wrong@example.com"
		svc.SendEmailCode(ctx, email, "login")
		err := svc.VerifyEmailCode(ctx, email, "login", "000000")
		if err == nil {
			t.Error("Expected error for wrong code")
		}
	})

	t.Run("Verify email code with empty code", func(t *testing.T) {
		err := svc.VerifyEmailCode(ctx, "test@example.com", "login", "")
		if err == nil {
			t.Error("Expected error for empty code")
		}
	})

	t.Run("Verify email code expired", func(t *testing.T) {
		err := svc.VerifyEmailCode(ctx, "nonexistent@example.com", "login", "123456")
		if err == nil {
			t.Error("Expected error for expired/missing code")
		}
	})
}

// =============================================================================
// SMS Code Service Tests - Extended
// =============================================================================

func TestSMSCodeService_SendCode(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
	provider := &service.MockSMSProvider{}
	cfg := service.DefaultSMSCodeConfig()
	svc := service.NewSMSCodeService(provider, cacheManager, cfg)
	ctx := context.Background()

	t.Run("Send code success", func(t *testing.T) {
		req := &service.SendCodeRequest{
			Phone:   "13800138000",
			Purpose: "login",
		}
		resp, err := svc.SendCode(ctx, req)
		if err != nil {
			t.Fatalf("SendCode failed: %v", err)
		}
		if resp == nil {
			t.Error("Expected response")
		}
	})

	t.Run("Send code with invalid phone", func(t *testing.T) {
		req := &service.SendCodeRequest{
			Phone:   "invalid",
			Purpose: "login",
		}
		_, err := svc.SendCode(ctx, req)
		if err == nil {
			t.Error("Expected error for invalid phone")
		}
	})

	t.Run("Send code with nil request", func(t *testing.T) {
		_, err := svc.SendCode(ctx, nil)
		if err == nil {
			t.Error("Expected error for nil request")
		}
	})

	t.Run("Send code with cooldown", func(t *testing.T) {
		phone := "13900139000"
		req := &service.SendCodeRequest{Phone: phone, Purpose: "login"}
		svc.SendCode(ctx, req)
		// Second request should hit cooldown
		_, err := svc.SendCode(ctx, req)
		if err == nil {
			t.Error("Expected rate limit error due to cooldown")
		}
	})
}

func TestSMSCodeService_VerifyCode(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
	provider := &service.MockSMSProvider{}
	cfg := service.DefaultSMSCodeConfig()
	svc := service.NewSMSCodeService(provider, cacheManager, cfg)
	ctx := context.Background()

	t.Run("Verify code success", func(t *testing.T) {
		phone := "13700137000"
		req := &service.SendCodeRequest{Phone: phone, Purpose: "login"}
		svc.SendCode(ctx, req)
		// Get code from cache
		val, ok := cacheManager.Get(ctx, "sms_code:login:"+phone)
		if !ok {
			t.Fatal("Code not found in cache")
		}
		code := val.(string)
		err := svc.VerifyCode(ctx, phone, "login", code)
		if err != nil {
			t.Fatalf("VerifyCode failed: %v", err)
		}
	})

	t.Run("Verify code with wrong code", func(t *testing.T) {
		phone := "13600136000"
		req := &service.SendCodeRequest{Phone: phone, Purpose: "login"}
		svc.SendCode(ctx, req)
		err := svc.VerifyCode(ctx, phone, "login", "000000")
		if err == nil {
			t.Error("Expected error for wrong code")
		}
	})

	t.Run("Verify code with empty code", func(t *testing.T) {
		err := svc.VerifyCode(ctx, "13800138000", "login", "")
		if err == nil {
			t.Error("Expected error for empty code")
		}
	})

	t.Run("Verify code expired", func(t *testing.T) {
		err := svc.VerifyCode(ctx, "19999999999", "login", "123456")
		if err == nil {
			t.Error("Expected error for expired code")
		}
	})
}

func TestSMSCodeService_NilService(t *testing.T) {
	var nilSvc *service.SMSCodeService
	ctx := context.Background()

	t.Run("SendCode with nil service", func(t *testing.T) {
		_, err := nilSvc.SendCode(ctx, &service.SendCodeRequest{Phone: "13800138000"})
		if err == nil {
			t.Error("Expected error for nil service")
		}
	})

	t.Run("VerifyCode with nil service", func(t *testing.T) {
		err := nilSvc.VerifyCode(ctx, "13800138000", "login", "123456")
		if err == nil {
			t.Error("Expected error for nil service")
		}
	})
}

// =============================================================================
// Email Activation Service Tests
// =============================================================================

func TestEmailActivationService_SendActivationEmail(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
	provider := &service.MockEmailProvider{}
	svc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite")
	ctx := context.Background()

	t.Run("Send activation email success", func(t *testing.T) {
		err := svc.SendActivationEmail(ctx, 1, "test@example.com", "testuser")
		if err != nil {
			t.Fatalf("SendActivationEmail failed: %v", err)
		}
	})
}

func TestEmailActivationService_ValidateActivationToken(t *testing.T) {
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
	provider := &service.MockEmailProvider{}
	svc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite")
	ctx := context.Background()

	t.Run("Validate activation token invalid", func(t *testing.T) {
		_, err := svc.ValidateActivationToken(ctx, "invalid_token")
		if err == nil {
			t.Error("Expected error for invalid token")
		}
	})

	t.Run("Validate activation token empty", func(t *testing.T) {
		_, err := svc.ValidateActivationToken(ctx, "")
		if err == nil {
			t.Error("Expected error for empty token")
		}
	})

	t.Run("Validate activation token success", func(t *testing.T) {
		// Send activation email first to create token
		svc.SendActivationEmail(ctx, 123, "activate@example.com", "testuser")
		// Find the token in cache
		// We can't directly enumerate keys, so we test with known token
		// This is a limitation of the test approach
		// In practice, we'd need to either expose the token or mock the cache
	})
}
