package service_test

import (
	"context"
	"fmt"
	"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"
	"github.com/user-management-system/internal/service"
	gormsqlite "gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// =============================================================================
// Auth Email Service Tests
// =============================================================================

func setupAuthEmailTestEnv(t *testing.T) (*service.AuthService, *gorm.DB) {
	t.Helper()

	dsn := fmt.Sprintf("file:auth_email_test_%d?mode=memory&cache=shared", time.Now().UnixNano())
	db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
		DriverName: "sqlite",
		DSN:        dsn,
	}), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		t.Fatalf("failed to connect database: %v", err)
	}

	if err := db.AutoMigrate(&domain.User{}, &domain.Role{}, &domain.UserRole{}); err != nil {
		t.Fatalf("failed to migrate: %v", err)
	}

	// Create predefined roles
	for _, role := range domain.PredefinedRoles {
		db.Create(&role)
	}

	jwtManager, _ := auth.NewJWTWithOptions(auth.JWTOptions{
		HS256Secret:        fmt.Sprintf("test-secret-%d", time.Now().UnixNano()),
		AccessTokenExpire:  15 * time.Minute,
		RefreshTokenExpire: 7 * 24 * time.Hour,
	})

	userRepo := repository.NewUserRepository(db)
	userRoleRepo := repository.NewUserRoleRepository(db)
	roleRepo := repository.NewRoleRepository(db)
	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

	svc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute)
	svc.SetRoleRepositories(userRoleRepo, roleRepo)

	return svc, db
}

func TestAuthService_SetEmailActivationService(t *testing.T) {
	svc, _ := setupAuthEmailTestEnv(t)

	t.Run("Set email activation service", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite")
		svc.SetEmailActivationService(emailActivationSvc)
		// No error means success
	})
}

func TestAuthService_SetEmailCodeService(t *testing.T) {
	svc, _ := setupAuthEmailTestEnv(t)

	t.Run("Set email code service", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		cfg := service.DefaultEmailCodeConfig()
		emailCodeSvc := service.NewEmailCodeService(provider, cacheManager, cfg)
		svc.SetEmailCodeService(emailCodeSvc)
		// No error means success
	})
}

func TestAuthService_HasEmailCodeService(t *testing.T) {
	svc, _ := setupAuthEmailTestEnv(t)

	t.Run("Has email code service false", func(t *testing.T) {
		if svc.HasEmailCodeService() {
			t.Error("Expected false for service without email code service")
		}
	})

	t.Run("Has email code service true", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		cfg := service.DefaultEmailCodeConfig()
		emailCodeSvc := service.NewEmailCodeService(provider, cacheManager, cfg)
		svc.SetEmailCodeService(emailCodeSvc)
		if !svc.HasEmailCodeService() {
			t.Error("Expected true after setting email code service")
		}
	})

	t.Run("Has email code service nil", func(t *testing.T) {
		var nilSvc *service.AuthService
		if nilSvc.HasEmailCodeService() {
			t.Error("Expected false for nil service")
		}
	})
}

func TestAuthService_SendEmailLoginCode(t *testing.T) {
	svc, db := setupAuthEmailTestEnv(t)
	ctx := context.Background()

	// Create test user with email
	email := "logincode@test.com"
	user := &domain.User{
		Username: "logincodeuser",
		Email:    &email,
		Status:   domain.UserStatusActive,
	}
	db.Create(user)

	t.Run("Send email login code without service configured", func(t *testing.T) {
		err := svc.SendEmailLoginCode(ctx, "test@test.com")
		if err == nil {
			t.Error("Expected error when email code service not configured")
		}
	})

	t.Run("Send email login code with service", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		cfg := service.DefaultEmailCodeConfig()
		emailCodeSvc := service.NewEmailCodeService(provider, cacheManager, cfg)
		svc.SetEmailCodeService(emailCodeSvc)

		err := svc.SendEmailLoginCode(ctx, email)
		if err != nil {
			t.Fatalf("SendEmailLoginCode failed: %v", err)
		}
	})

	t.Run("Send email login code for non-existent email", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		cfg := service.DefaultEmailCodeConfig()
		emailCodeSvc := service.NewEmailCodeService(provider, cacheManager, cfg)
		svc.SetEmailCodeService(emailCodeSvc)

		// Should return nil to avoid user enumeration
		err := svc.SendEmailLoginCode(ctx, "nonexistent@test.com")
		if err != nil {
			t.Fatalf("Expected nil for non-existent email, got: %v", err)
		}
	})
}

func TestAuthService_LoginByEmailCode(t *testing.T) {
	svc, db := setupAuthEmailTestEnv(t)
	ctx := context.Background()

	// Create test user with email
	email := "emailcode@test.com"
	user := &domain.User{
		Username: "emailcodeuser",
		Email:    &email,
		Status:   domain.UserStatusActive,
	}
	db.Create(user)

	t.Run("Login by email code without service", func(t *testing.T) {
		_, err := svc.LoginByEmailCode(ctx, email, "123456", "127.0.0.1")
		if err == nil {
			t.Error("Expected error when email code service not configured")
		}
	})

	t.Run("Login by email code with invalid code", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		cfg := service.DefaultEmailCodeConfig()
		emailCodeSvc := service.NewEmailCodeService(provider, cacheManager, cfg)
		svc.SetEmailCodeService(emailCodeSvc)

		_, err := svc.LoginByEmailCode(ctx, email, "invalid", "127.0.0.1")
		if err == nil {
			t.Error("Expected error for invalid code")
		}
	})
}

func TestAuthService_ActivateEmail(t *testing.T) {
	svc, db := setupAuthEmailTestEnv(t)
	ctx := context.Background()

	t.Run("Activate email without service", func(t *testing.T) {
		err := svc.ActivateEmail(ctx, "token")
		if err == nil {
			t.Error("Expected error when email activation service not configured")
		}
	})

	t.Run("Activate email with invalid token", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite")
		svc.SetEmailActivationService(emailActivationSvc)

		err := svc.ActivateEmail(ctx, "invalid_token")
		if err == nil {
			t.Error("Expected error for invalid token")
		}
	})

	t.Run("Activate email for already active user", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite")
		svc.SetEmailActivationService(emailActivationSvc)

		// Create inactive user and send activation
		email := "activate@test.com"
		user := &domain.User{
			Username: "activateuser",
			Email:    &email,
			Status:   domain.UserStatusActive,
		}
		db.Create(user)

		// Manually store a token in cache
		cacheManager.Set(ctx, "email_activation:test_token_active", user.ID, 24*60*60*1000000000, 24*60*60*1000000000)

		err := svc.ActivateEmail(ctx, "test_token_active")
		if err == nil {
			t.Error("Expected error for already active user")
		}
	})
}

func TestAuthService_ResendActivationEmail(t *testing.T) {
	svc, db := setupAuthEmailTestEnv(t)
	ctx := context.Background()

	t.Run("Resend activation without service", func(t *testing.T) {
		err := svc.ResendActivationEmail(ctx, "test@test.com")
		if err == nil {
			t.Error("Expected error when email activation service not configured")
		}
	})

	t.Run("Resend activation for non-existent email", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite")
		svc.SetEmailActivationService(emailActivationSvc)

		// Should return nil to avoid user enumeration
		err := svc.ResendActivationEmail(ctx, "nonexistent@test.com")
		if err != nil {
			t.Errorf("Expected nil for non-existent email, got: %v", err)
		}
	})

	t.Run("Resend activation for active user", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite")
		svc.SetEmailActivationService(emailActivationSvc)

		email := "resendactive@test.com"
		user := &domain.User{
			Username: "resendactiveuser",
			Email:    &email,
			Status:   domain.UserStatusActive,
		}
		db.Create(user)

		// Should return nil for active user
		err := svc.ResendActivationEmail(ctx, email)
		if err != nil {
			t.Errorf("Expected nil for active user, got: %v", err)
		}
	})

	t.Run("Resend activation for inactive user", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		provider := &service.MockEmailProvider{}
		emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite")
		svc.SetEmailActivationService(emailActivationSvc)

		email := "resendinactive@test.com"
		user := &domain.User{
			Username: "resendinactiveuser",
			Email:    &email,
			Status:   domain.UserStatusInactive,
		}
		db.Create(user)

		err := svc.ResendActivationEmail(ctx, email)
		if err != nil {
			t.Fatalf("ResendActivationEmail failed: %v", err)
		}
	})
}

func TestAuthService_RegisterWithActivation(t *testing.T) {
	svc, _ := setupAuthEmailTestEnv(t)
	ctx := context.Background()

	t.Run("Register with activation success", func(t *testing.T) {
		req := &service.RegisterRequest{
			Username: "regactuser",
			Password: "Password123!",
			Email:    "regact@test.com",
		}
		userInfo, err := svc.RegisterWithActivation(ctx, req)
		if err != nil {
			t.Fatalf("RegisterWithActivation failed: %v", err)
		}
		if userInfo == nil {
			t.Error("Expected user info")
		}
	})

	t.Run("Register with weak password", func(t *testing.T) {
		req := &service.RegisterRequest{
			Username: "weakpwduser",
			Password: "123",
		}
		_, err := svc.RegisterWithActivation(ctx, req)
		if err == nil {
			t.Error("Expected error for weak password")
		}
	})

	t.Run("Register with duplicate username", func(t *testing.T) {
		req := &service.RegisterRequest{
			Username: "regactuser", // Already exists
			Password: "Password123!",
		}
		_, err := svc.RegisterWithActivation(ctx, req)
		if err == nil {
			t.Error("Expected error for duplicate username")
		}
	})
}

// =============================================================================
// Login By Email Code Extended Tests
// =============================================================================

func TestAuthService_LoginByEmailCode_Extended(t *testing.T) {
	svc, _ := setupAuthEmailTestEnv(t)
	ctx := context.Background()

	t.Run("LoginByEmailCode without email code service", func(t *testing.T) {
		_, err := svc.LoginByEmailCode(ctx, "test@example.com", "code123", "127.0.0.1")
		if err == nil {
			t.Error("Expected error when email code service not configured")
		}
	})

	t.Run("LoginByEmailCode with empty email", func(t *testing.T) {
		// Create a service with email code service
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		emailProvider := &service.MockEmailProvider{}
		emailCodeSvc := service.NewEmailCodeService(emailProvider, cacheManager, service.DefaultEmailCodeConfig())
		svc.SetEmailCodeService(emailCodeSvc)

		_, err := svc.LoginByEmailCode(ctx, "", "code123", "127.0.0.1")
		if err == nil {
			t.Error("Expected error for empty email")
		}
	})

	t.Run("LoginByEmailCode for non-existent user", func(t *testing.T) {
		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)
		emailProvider := &service.MockEmailProvider{}
		emailCodeSvc := service.NewEmailCodeService(emailProvider, cacheManager, service.DefaultEmailCodeConfig())
		svc.SetEmailCodeService(emailCodeSvc)

		// Store a valid code
		cacheManager.Set(ctx, fmt.Sprintf("email_code:login:%s", "nonexistent@test.com"), "123456", time.Minute*5, time.Minute*5)

		_, err := svc.LoginByEmailCode(ctx, "nonexistent@test.com", "123456", "127.0.0.1")
		if err == nil {
			t.Error("Expected error for non-existent user")
		}
	})
}

// =============================================================================
// Register With Activation Extended Tests
// =============================================================================

func TestAuthService_RegisterWithActivation_Extended(t *testing.T) {
	svc, _ := setupAuthEmailTestEnv(t)
	ctx := context.Background()

	t.Run("Register with duplicate email", func(t *testing.T) {
		// Create first user
		req1 := &service.RegisterRequest{
			Username: "dupemailuser1",
			Password: "Password123!",
			Email:    "dup@test.com",
		}
		svc.RegisterWithActivation(ctx, req1)

		// Try to register with same email
		req2 := &service.RegisterRequest{
			Username: "dupemailuser2",
			Password: "Password123!",
			Email:    "dup@test.com",
		}
		_, err := svc.RegisterWithActivation(ctx, req2)
		if err == nil {
			t.Error("Expected error for duplicate email")
		}
	})

	t.Run("Register with phone", func(t *testing.T) {
		phone := "13800138000"
		req := &service.RegisterRequest{
			Username: "phoneuser",
			Password: "Password123!",
			Phone:    phone,
		}
		_, err := svc.RegisterWithActivation(ctx, req)
		// Phone registration requires SMS verification which is not configured
		if err == nil {
			t.Error("Expected error for phone registration without SMS service")
		}
	})
}
