package service_test

import (
	"context"
	"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 Capabilities Tests - Phase 1
// =============================================================================

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

	dsn := "file:cap_test?mode=memory&cache=shared"
	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)
	}

	// Seed roles
	db.Create(&domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusEnabled})
	db.Create(&domain.Role{Code: "user", Name: "用户", Status: domain.RoleStatusEnabled})

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

	l1Cache := cache.NewL1Cache()
	l2Cache := cache.NewRedisCache(false)
	cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

	userRepo := repository.NewUserRepository(db)
	roleRepo := repository.NewRoleRepository(db)
	userRoleRepo := repository.NewUserRoleRepository(db)

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

	return authSvc, db
}

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

	t.Run("SupportsEmailActivation", func(t *testing.T) {
		if svc.SupportsEmailActivation() {
			t.Error("Should not support email activation without config")
		}
	})

	t.Run("SupportsEmailCodeLogin", func(t *testing.T) {
		if svc.SupportsEmailCodeLogin() {
			t.Error("Should not support email code login without config")
		}
	})

	t.Run("SupportsSMSCodeLogin", func(t *testing.T) {
		if svc.SupportsSMSCodeLogin() {
			t.Error("Should not support SMS code login without config")
		}
	})

	t.Run("GetAuthCapabilities", func(t *testing.T) {
		caps := svc.GetAuthCapabilities(ctx)
		if !caps.Password {
			t.Error("Password should always be true")
		}
	})

	t.Run("GetAuthCapabilities with nil ctx", func(t *testing.T) {
		caps := svc.GetAuthCapabilities(nil)
		if !caps.Password {
			t.Error("Password should always be true")
		}
	})

	t.Run("IsAdminBootstrapRequired with nil ctx", func(t *testing.T) {
		// 测试nil ctx不会panic
		_ = svc.IsAdminBootstrapRequired(nil)
	})

	t.Run("nil service methods", func(t *testing.T) {
		var nilSvc *service.AuthService

		if nilSvc.SupportsEmailActivation() {
			t.Error("nil service should return false")
		}
		if nilSvc.SupportsEmailCodeLogin() {
			t.Error("nil service should return false")
		}
		if nilSvc.SupportsSMSCodeLogin() {
			t.Error("nil service should return false")
		}
		if nilSvc.IsAdminBootstrapRequired(ctx) {
			t.Error("nil service should return false")
		}
	})
}

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

	t.Run("Admin bootstrap required when no admin", func(t *testing.T) {
		required := svc.IsAdminBootstrapRequired(ctx)
		// Should be true since no admin user exists
		if !required {
			t.Log("Admin bootstrap should be required when no admin exists")
		}
	})
}

// Test nil service behavior
func TestAuthService_NilBehavior(t *testing.T) {
	ctx := context.Background()
	var nilSvc *service.AuthService

	t.Run("nil service RefreshToken", func(t *testing.T) {
		_, err := nilSvc.RefreshToken(ctx, "token")
		if err == nil {
			t.Error("nil service should return error")
		}
	})

	t.Run("nil service GetUserInfo", func(t *testing.T) {
		_, err := nilSvc.GetUserInfo(ctx, 1)
		if err == nil {
			t.Error("nil service should return error")
		}
	})

	t.Run("nil service Logout", func(t *testing.T) {
		err := nilSvc.Logout(ctx, "user", nil)
		if err != nil {
			t.Errorf("nil service Logout should not error: %v", err)
		}
	})

	t.Run("nil service IsTokenBlacklisted", func(t *testing.T) {
		blacklisted := nilSvc.IsTokenBlacklisted(ctx, "jti")
		if blacklisted {
			t.Error("nil service should return false")
		}
	})

	t.Run("nil service GetAuthCapabilities", func(t *testing.T) {
		caps := nilSvc.GetAuthCapabilities(ctx)
		// nil service returns empty capabilities, Password is false
		_ = caps
		t.Logf("nil service GetAuthCapabilities: %+v", caps)
	})

	t.Run("nil service RefreshTokenTTLSeconds", func(t *testing.T) {
		ttl := nilSvc.RefreshTokenTTLSeconds()
		if ttl != 0 {
			t.Errorf("nil service should return 0, got %d", ttl)
		}
	})
}

// =============================================================================
// IsAdminBootstrapRequired Tests
// =============================================================================

func TestAuthService_IsAdminBootstrapRequired(t *testing.T) {
	t.Run("nil service returns false", func(t *testing.T) {
		var nilSvc *service.AuthService
		result := nilSvc.IsAdminBootstrapRequired(context.Background())
		if result {
			t.Error("nil service should return false")
		}
	})

	t.Run("service without role repo returns false", func(t *testing.T) {
		svc := &service.AuthService{}
		result := svc.IsAdminBootstrapRequired(context.Background())
		if result {
			t.Error("service without role repo should return false")
		}
	})
}

// =============================================================================
// IsAdminBootstrapRequired Extended Tests
// =============================================================================

func TestAuthService_IsAdminBootstrapRequired_Extended(t *testing.T) {
	t.Run("returns true when admin role not found", func(t *testing.T) {
		db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
			DriverName: "sqlite",
			DSN:        "file:cap_test_no_role?mode=memory&cache=shared",
		}), &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)
		}
		// Do NOT create admin role

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

		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

		userRepo := repository.NewUserRepository(db)
		roleRepo := repository.NewRoleRepository(db)
		userRoleRepo := repository.NewUserRoleRepository(db)

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

		result := authSvc.IsAdminBootstrapRequired(context.Background())
		if !result {
			t.Error("Should return true when admin role not found")
		}
	})

	t.Run("returns true when admin role exists but no users assigned", func(t *testing.T) {
		db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
			DriverName: "sqlite",
			DSN:        "file:cap_test_no_users?mode=memory&cache=shared",
		}), &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 admin role but no users
		db.Create(&domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusEnabled})

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

		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

		userRepo := repository.NewUserRepository(db)
		roleRepo := repository.NewRoleRepository(db)
		userRoleRepo := repository.NewUserRoleRepository(db)

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

		result := authSvc.IsAdminBootstrapRequired(context.Background())
		if !result {
			t.Error("Should return true when no admin users assigned")
		}
	})

	t.Run("returns false when active admin user exists", func(t *testing.T) {
		db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
			DriverName: "sqlite",
			DSN:        "file:cap_test_active_admin?mode=memory&cache=shared",
		}), &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 admin role
		adminRole := &domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusEnabled}
		db.Create(adminRole)

		// Create active admin user
		adminUser := &domain.User{
			Username: "admin",
			Password: "hashed",
			Status:   domain.UserStatusActive,
		}
		db.Create(adminUser)

		// Assign admin role
		db.Create(&domain.UserRole{UserID: adminUser.ID, RoleID: adminRole.ID})

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

		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

		userRepo := repository.NewUserRepository(db)
		roleRepo := repository.NewRoleRepository(db)
		userRoleRepo := repository.NewUserRoleRepository(db)

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

		result := authSvc.IsAdminBootstrapRequired(context.Background())
		if result {
			t.Error("Should return false when active admin user exists")
		}
	})

	t.Run("returns true when admin user is not active", func(t *testing.T) {
		db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
			DriverName: "sqlite",
			DSN:        "file:cap_test_inactive_admin?mode=memory&cache=shared",
		}), &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 admin role
		adminRole := &domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusEnabled}
		db.Create(adminRole)

		// Create inactive admin user
		adminUser := &domain.User{
			Username: "admin",
			Password: "hashed",
			Status:   domain.UserStatusInactive,
		}
		db.Create(adminUser)

		// Assign admin role
		db.Create(&domain.UserRole{UserID: adminUser.ID, RoleID: adminRole.ID})

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

		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

		userRepo := repository.NewUserRepository(db)
		roleRepo := repository.NewRoleRepository(db)
		userRoleRepo := repository.NewUserRoleRepository(db)

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

		result := authSvc.IsAdminBootstrapRequired(context.Background())
		if !result {
			t.Error("Should return true when admin user is not active")
		}
	})

	t.Run("returns true when admin user is locked", func(t *testing.T) {
		db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
			DriverName: "sqlite",
			DSN:        "file:cap_test_locked_admin?mode=memory&cache=shared",
		}), &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 admin role
		adminRole := &domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusEnabled}
		db.Create(adminRole)

		// Create locked admin user
		adminUser := &domain.User{
			Username: "admin",
			Password: "hashed",
			Status:   domain.UserStatusLocked,
		}
		db.Create(adminUser)

		// Assign admin role
		db.Create(&domain.UserRole{UserID: adminUser.ID, RoleID: adminRole.ID})

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

		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

		userRepo := repository.NewUserRepository(db)
		roleRepo := repository.NewRoleRepository(db)
		userRoleRepo := repository.NewUserRoleRepository(db)

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

		result := authSvc.IsAdminBootstrapRequired(context.Background())
		if !result {
			t.Error("Should return true when admin user is locked")
		}
	})

	t.Run("returns true when admin role is disabled", func(t *testing.T) {
		db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
			DriverName: "sqlite",
			DSN:        "file:cap_test_disabled_role?mode=memory&cache=shared",
		}), &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 disabled admin role
		adminRole := &domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusDisabled}
		db.Create(adminRole)

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

		l1Cache := cache.NewL1Cache()
		l2Cache := cache.NewRedisCache(false)
		cacheManager := cache.NewCacheManager(l1Cache, l2Cache)

		userRepo := repository.NewUserRepository(db)
		roleRepo := repository.NewRoleRepository(db)
		userRoleRepo := repository.NewUserRoleRepository(db)

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

		result := authSvc.IsAdminBootstrapRequired(context.Background())
		if !result {
			t.Error("Should return true when admin role is disabled")
		}
	})
}
