package service

import (
	"context"
	"errors"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/ocdp/cluster-service/internal/domain/entity"
	"github.com/ocdp/cluster-service/internal/domain/repository"
	"github.com/ocdp/cluster-service/internal/pkg/authz"
	jwtpkg "github.com/ocdp/cluster-service/internal/pkg/jwt"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/util/validation"
)

// AuthService 认证领域服务
type AuthService struct {
	userRepo       repository.UserRepository
	workspaceRepo  repository.WorkspaceRepository
	instanceRepo   repository.InstanceRepository
	clusterRepo    repository.ClusterRepository
	bindingRepo    repository.WorkspaceClusterBindingRepository
	tenantClient   repository.TenantKubeClient
	passwordHasher PasswordHasher
	tokenGenerator TokenGenerator
}

// PasswordHasher 密码哈希接口
type PasswordHasher interface {
	Hash(password string) (string, error)
	Verify(password, hash string) error
}

// TokenGenerator Token 生成器接口
type TokenGenerator interface {
	Generate(userID, username, role, workspaceID string) (accessToken, refreshToken string, err error)
	Verify(token string) (userID, username string, err error)
	VerifyWithIssuedAt(token string) (userID, username string, issuedAt int64, err error)
	VerifyAccess(token string) (*jwtpkg.Claims, error)
	VerifyRefresh(token string) (*jwtpkg.Claims, error)
	Refresh(refreshToken string) (newAccessToken string, err error)
}

// NewAuthService 创建认证服务
func NewAuthService(
	userRepo repository.UserRepository,
	workspaceRepo repository.WorkspaceRepository,
	passwordHasher PasswordHasher,
	tokenGenerator TokenGenerator,
) *AuthService {
	return &AuthService{
		userRepo:       userRepo,
		workspaceRepo:  workspaceRepo,
		passwordHasher: passwordHasher,
		tokenGenerator: tokenGenerator,
	}
}

func (s *AuthService) SetUserLifecycleCleanup(
	instanceRepo repository.InstanceRepository,
	clusterRepo repository.ClusterRepository,
	bindingRepo repository.WorkspaceClusterBindingRepository,
	tenantClient repository.TenantKubeClient,
) {
	s.instanceRepo = instanceRepo
	s.clusterRepo = clusterRepo
	s.bindingRepo = bindingRepo
	s.tenantClient = tenantClient
}

// Register 注册新用户。业务入口只允许 admin 调用；初始 admin 由 bootstrap seeder 创建。
type UserWorkspaceOptions struct {
	Namespace        string
	DefaultClusterID string
	QuotaCPU         string
	QuotaMemory      string
	QuotaGPU         string
	QuotaGPUMem      string
}

// IsAdminExists checks whether any admin user already exists in the database.
func (s *AuthService) IsAdminExists(ctx context.Context) (bool, error) {
	users, err := s.userRepo.List(ctx)
	if err != nil {
		return false, err
	}
	for _, u := range users {
		if u.Role == authz.RoleAdmin {
			return true, nil
		}
	}
	return false, nil
}

// SetupInitialAdmin creates the first admin user. Fails if an admin already exists.
func (s *AuthService) SetupInitialAdmin(ctx context.Context, username, password, email string) (*entity.User, error) {
	hasAdmin, err := s.IsAdminExists(ctx)
	if err != nil {
		return nil, err
	}
	if hasAdmin {
		return nil, entity.ErrForbidden
	}

	// Hash password
	passwordHash, err := s.passwordHasher.Hash(password)
	if err != nil {
		return nil, err
	}

	if email == "" {
		email = username + "@local.ocdp"
	}

	user := entity.NewUser(username, passwordHash, email)
	user.ID = uuid.New().String()
	user.Role = authz.RoleAdmin
	user.WorkspaceID = entity.DefaultWorkspaceID
	user.IsActive = true

	if err := user.Validate(); err != nil {
		return nil, err
	}
	if err := s.userRepo.Create(ctx, user); err != nil {
		return nil, err
	}
	return user, nil
}

func (s *AuthService) Register(ctx context.Context, username, password, role, workspaceID string, opts UserWorkspaceOptions, isActive, mustChangePassword *bool) (*entity.User, error) {
	principal, err := authz.RequirePrincipal(ctx)
	if err != nil {
		return nil, entity.ErrUnauthorized
	}
	if !principal.IsAdmin() {
		return nil, entity.ErrForbidden
	}

	// 检查用户是否已存在
	existingUser, _ := s.userRepo.GetByUsername(ctx, username)
	if existingUser != nil {
		return nil, entity.ErrUserExists
	}

	// 哈希密码
	passwordHash, err := s.passwordHasher.Hash(password)
	if err != nil {
		return nil, err
	}
	normalizedOpts, err := normalizeQuotaOptions(opts)
	if err != nil {
		return nil, err
	}
	if normalizeUserRole(role) == authz.RoleUser {
		normalizedOpts = defaultUserQuotaOptions(normalizedOpts)
	}

	// 默认生成占位邮箱，避免数据库约束失败
	email := username + "@local.ocdp"

	// 创建用户
	user := entity.NewUser(username, passwordHash, email)
	user.ID = uuid.New().String()
	user.Role = normalizeUserRole(role)
	user.WorkspaceID = workspaceID
	if user.Role == authz.RoleUser {
		workspace, err := s.createUserWorkspace(ctx, username, principal.UserID, normalizedOpts)
		if err != nil {
			return nil, err
		}
		user.WorkspaceID = workspace.ID
	}
	if user.WorkspaceID == "" {
		user.WorkspaceID = entity.DefaultWorkspaceID
	}
	if user.Role == authz.RoleAdmin {
		user.WorkspaceID = entity.DefaultWorkspaceID
	}
	if isActive != nil {
		user.IsActive = *isActive
	}
	if mustChangePassword != nil {
		user.MustChangePassword = *mustChangePassword
	}

	if err := user.Validate(); err != nil {
		return nil, err
	}

	if err := s.userRepo.Create(ctx, user); err != nil {
		return nil, err
	}

	return user, nil
}

func (s *AuthService) createUserWorkspace(ctx context.Context, username, createdBy string, opts UserWorkspaceOptions) (*entity.Workspace, error) {
	if s.workspaceRepo == nil {
		return nil, entity.ErrWorkspaceNotFound
	}
	name := userWorkspaceName(username)
	namespace := strings.TrimSpace(opts.Namespace)
	if namespace == "" {
		namespace = entity.NamespaceForUser(username)
	}
	if namespace != "" {
		if len(validation.IsDNS1123Label(namespace)) > 0 {
			return nil, entity.ErrInvalidNamespace
		}
	}
	if existing, err := s.workspaceRepo.GetByName(ctx, name); err == nil && existing != nil {
		if namespace != "" && existing.K8sNamespace != namespace {
			if err := s.ensureNamespaceAvailable(ctx, namespace, existing.ID); err != nil {
				return nil, err
			}
		}
		applyWorkspaceOptions(existing, opts)
		if namespace != "" {
			existing.K8sNamespace = namespace
			existing.K8sSAName = entity.ServiceAccountForNamespace(namespace)
		}
		if err := s.workspaceRepo.Update(ctx, existing); err != nil {
			return nil, err
		}
		return existing, nil
	} else if err != nil && !errors.Is(err, entity.ErrWorkspaceNotFound) {
		return nil, err
	}
	if err := s.ensureNamespaceAvailable(ctx, namespace, ""); err != nil {
		return nil, err
	}
	workspace := entity.NewWorkspace(name, createdBy)
	workspace.ID = uuid.New().String()
	workspace.DefaultClusterID = strings.TrimSpace(opts.DefaultClusterID)
	if namespace != "" {
		workspace.K8sNamespace = namespace
		workspace.K8sSAName = entity.ServiceAccountForNamespace(namespace)
	}
	workspace.QuotaCPU = strings.TrimSpace(opts.QuotaCPU)
	workspace.QuotaMemory = strings.TrimSpace(opts.QuotaMemory)
	workspace.QuotaGPU = strings.TrimSpace(opts.QuotaGPU)
	workspace.QuotaGPUMem = strings.TrimSpace(opts.QuotaGPUMem)
	if err := s.workspaceRepo.Create(ctx, workspace); err != nil {
		if errors.Is(err, entity.ErrWorkspaceExists) {
			existing, getErr := s.workspaceRepo.GetByName(ctx, name)
			if getErr != nil {
				return nil, err
			}
			if existing.K8sNamespace != namespace {
				return nil, entity.ErrWorkspaceNamespaceConflict
			}
			return existing, nil
		}
		return nil, err
	}
	return workspace, nil
}

func userWorkspaceName(username string) string {
	return strings.TrimPrefix(entity.NamespaceForUser(username), "ocdp-u-")
}

func (s *AuthService) ensureNamespaceAvailable(ctx context.Context, namespace, allowedWorkspaceID string) error {
	if s.workspaceRepo == nil || strings.TrimSpace(namespace) == "" {
		return nil
	}
	workspaces, err := s.workspaceRepo.List(ctx)
	if err != nil {
		return err
	}
	for _, workspace := range workspaces {
		if workspace == nil || workspace.K8sNamespace != namespace {
			continue
		}
		if allowedWorkspaceID != "" && workspace.ID == allowedWorkspaceID {
			continue
		}
		return entity.ErrWorkspaceNamespaceConflict
	}
	return nil
}

func normalizeQuotaOptions(opts UserWorkspaceOptions) (UserWorkspaceOptions, error) {
	opts.Namespace = strings.TrimSpace(opts.Namespace)
	opts.DefaultClusterID = strings.TrimSpace(opts.DefaultClusterID)
	opts.QuotaCPU = normalizeStandardQuotaQuantity(opts.QuotaCPU)
	opts.QuotaMemory = normalizeStandardQuotaQuantity(opts.QuotaMemory)
	opts.QuotaGPU = normalizeStandardQuotaQuantity(opts.QuotaGPU)
	gpuMem, err := normalizeGPUMemoryQuota(opts.QuotaGPUMem)
	if err != nil {
		return opts, err
	}
	opts.QuotaGPUMem = gpuMem
	for _, value := range []string{opts.QuotaCPU, opts.QuotaMemory, opts.QuotaGPU} {
		if value == "" {
			continue
		}
		if _, err := resource.ParseQuantity(value); err != nil {
			return opts, entity.ErrInvalidTenantResourceQuota
		}
	}
	if opts.Namespace != "" && len(validation.IsDNS1123Label(opts.Namespace)) > 0 {
		return opts, entity.ErrInvalidNamespace
	}
	return opts, nil
}

func defaultUserQuotaOptions(opts UserWorkspaceOptions) UserWorkspaceOptions {
	if strings.TrimSpace(opts.QuotaGPU) == "" {
		opts.QuotaGPU = "0"
	}
	if strings.TrimSpace(opts.QuotaGPUMem) == "" {
		opts.QuotaGPUMem = "0"
	}
	return opts
}

func (s *AuthService) ListUsers(ctx context.Context) ([]*entity.User, error) {
	principal, err := authz.RequirePrincipal(ctx)
	if err != nil {
		return nil, entity.ErrUnauthorized
	}
	if !principal.IsAdmin() {
		return nil, entity.ErrForbidden
	}
	return s.userRepo.List(ctx)
}

func (s *AuthService) UpdateUser(ctx context.Context, userID, role, workspaceID string, opts UserWorkspaceOptions, isActive, mustChangePassword *bool) (*entity.User, error) {
	principal, err := authz.RequirePrincipal(ctx)
	if err != nil {
		return nil, entity.ErrUnauthorized
	}
	if !principal.IsAdmin() {
		return nil, entity.ErrForbidden
	}
	user, err := s.userRepo.GetByID(ctx, userID)
	if err != nil {
		return nil, entity.ErrUserNotFound
	}
	previousRole := user.Role
	if role != "" {
		user.Role = normalizeUserRole(role)
	}
	if workspaceID != "" && user.Role != authz.RoleUser {
		user.WorkspaceID = workspaceID
	}
	workspaceHandled := false
	if user.Role == authz.RoleAdmin {
		user.WorkspaceID = entity.DefaultWorkspaceID
	}
	if user.Role == authz.RoleUser && (role != "" || workspaceID != "" || hasWorkspaceUpdates(opts)) {
		normalizedOpts, err := normalizeQuotaOptions(opts)
		if err != nil {
			return nil, err
		}
		normalizedOpts = defaultUserQuotaOptions(normalizedOpts)
		currentWorkspace, _ := s.currentUserWorkspace(ctx, user)
		if currentWorkspace != nil && shouldCreatePrivateWorkspace(user, previousRole, currentWorkspace) {
			if normalizedOpts.Namespace == "" || normalizedOpts.Namespace == currentWorkspace.K8sNamespace {
				normalizedOpts.Namespace = ""
			}
		}
		workspace, err := s.ensureUserWorkspaceForUpdate(ctx, user, previousRole, currentWorkspace, opts, normalizedOpts, principal.UserID)
		if err != nil {
			return nil, err
		}
		user.WorkspaceID = workspace.ID
		workspaceHandled = true
	}
	if isActive != nil {
		if user.ID == principal.UserID && !*isActive {
			return nil, entity.ErrForbidden
		}
		user.IsActive = *isActive
	}
	if mustChangePassword != nil {
		user.MustChangePassword = *mustChangePassword
	}
	if user.Role != authz.RoleAdmin && !workspaceHandled && hasWorkspaceUpdates(opts) {
		normalizedOpts, err := normalizeQuotaOptions(opts)
		if err != nil {
			return nil, err
		}
		workspace, err := s.workspaceRepo.GetByID(ctx, user.WorkspaceID)
		if err != nil {
			return nil, err
		}
		applyWorkspaceOptionsForUpdate(workspace, opts, normalizedOpts)
		if err := s.workspaceRepo.Update(ctx, workspace); err != nil {
			return nil, err
		}
		if err := s.syncWorkspaceBindings(ctx, workspace); err != nil {
			return nil, err
		}
	}
	user.RevokedAfter = time.Now()
	user.UpdatedAt = time.Now()
	if err := user.Validate(); err != nil {
		return nil, err
	}
	if err := s.userRepo.Update(ctx, user); err != nil {
		return nil, err
	}
	return user, nil
}

func hasWorkspaceUpdates(opts UserWorkspaceOptions) bool {
	return strings.TrimSpace(opts.Namespace) != "" ||
		strings.TrimSpace(opts.DefaultClusterID) != "" ||
		strings.TrimSpace(opts.QuotaCPU) != "" ||
		strings.TrimSpace(opts.QuotaMemory) != "" ||
		strings.TrimSpace(opts.QuotaGPU) != "" ||
		strings.TrimSpace(opts.QuotaGPUMem) != ""
}

func applyWorkspaceOptions(workspace *entity.Workspace, opts UserWorkspaceOptions) {
	if namespace := strings.TrimSpace(opts.Namespace); namespace != "" {
		workspace.K8sNamespace = namespace
		workspace.K8sSAName = entity.ServiceAccountForNamespace(namespace)
	}
	if value := strings.TrimSpace(opts.DefaultClusterID); value != "" {
		workspace.DefaultClusterID = value
	}
	if value := strings.TrimSpace(opts.QuotaCPU); value != "" {
		workspace.QuotaCPU = value
	}
	if value := strings.TrimSpace(opts.QuotaMemory); value != "" {
		workspace.QuotaMemory = value
	}
	if value := strings.TrimSpace(opts.QuotaGPU); value != "" {
		workspace.QuotaGPU = value
	}
	if value := strings.TrimSpace(opts.QuotaGPUMem); value != "" {
		workspace.QuotaGPUMem = value
	}
}

func (s *AuthService) currentUserWorkspace(ctx context.Context, user *entity.User) (*entity.Workspace, error) {
	if s.workspaceRepo == nil || user == nil || user.WorkspaceID == "" {
		return nil, entity.ErrWorkspaceNotFound
	}
	return s.workspaceRepo.GetByID(ctx, user.WorkspaceID)
}

func shouldCreatePrivateWorkspace(user *entity.User, previousRole string, current *entity.Workspace) bool {
	if user == nil {
		return true
	}
	if previousRole == authz.RoleAdmin || user.WorkspaceID == "" || user.WorkspaceID == entity.DefaultWorkspaceID {
		return true
	}
	if current == nil {
		return true
	}
	return current.Name != userWorkspaceName(user.Username)
}

func (s *AuthService) ensureUserWorkspaceForUpdate(ctx context.Context, user *entity.User, previousRole string, current *entity.Workspace, rawOpts, normalizedOpts UserWorkspaceOptions, createdBy string) (*entity.Workspace, error) {
	if s.workspaceRepo == nil {
		return nil, entity.ErrWorkspaceNotFound
	}
	if shouldCreatePrivateWorkspace(user, previousRole, current) {
		return s.createUserWorkspace(ctx, user.Username, createdBy, normalizedOpts)
	}
	if rawNamespace := strings.TrimSpace(rawOpts.Namespace); rawNamespace != "" && rawNamespace != current.K8sNamespace {
		if err := s.ensureNamespaceAvailable(ctx, rawNamespace, current.ID); err != nil {
			return nil, err
		}
	}
	applyWorkspaceOptionsForUpdate(current, rawOpts, normalizedOpts)
	if err := s.workspaceRepo.Update(ctx, current); err != nil {
		return nil, err
	}
	if err := s.syncWorkspaceBindings(ctx, current); err != nil {
		return nil, err
	}
	return current, nil
}

func applyWorkspaceOptionsForUpdate(workspace *entity.Workspace, rawOpts, normalizedOpts UserWorkspaceOptions) {
	if namespace := strings.TrimSpace(rawOpts.Namespace); namespace != "" {
		workspace.K8sNamespace = namespace
		workspace.K8sSAName = entity.ServiceAccountForNamespace(namespace)
	}
	if strings.TrimSpace(rawOpts.DefaultClusterID) != "" {
		workspace.DefaultClusterID = normalizedOpts.DefaultClusterID
	}
	if strings.TrimSpace(rawOpts.QuotaCPU) != "" {
		workspace.QuotaCPU = normalizedOpts.QuotaCPU
	}
	if strings.TrimSpace(rawOpts.QuotaMemory) != "" {
		workspace.QuotaMemory = normalizedOpts.QuotaMemory
	}
	if strings.TrimSpace(rawOpts.QuotaGPU) != "" {
		workspace.QuotaGPU = normalizedOpts.QuotaGPU
	}
	if strings.TrimSpace(rawOpts.QuotaGPUMem) != "" {
		workspace.QuotaGPUMem = normalizedOpts.QuotaGPUMem
	}
}

func (s *AuthService) syncWorkspaceBindings(ctx context.Context, workspace *entity.Workspace) error {
	if workspace == nil || s.bindingRepo == nil {
		return nil
	}
	bindings, err := s.bindingRepo.ListByWorkspace(ctx, workspace.ID)
	if err != nil {
		return err
	}
	for _, binding := range bindings {
		if binding == nil {
			continue
		}
		binding.QuotaCPU = strings.TrimSpace(workspace.QuotaCPU)
		binding.QuotaMemory = strings.TrimSpace(workspace.QuotaMemory)
		binding.QuotaGPU = strings.TrimSpace(workspace.QuotaGPU)
		if binding.QuotaGPU == "" {
			binding.QuotaGPU = "0"
		}
		binding.QuotaGPUMem = strings.TrimSpace(workspace.QuotaGPUMem)
		if binding.QuotaGPUMem == "" {
			binding.QuotaGPUMem = "0"
		}
		binding.UpdatedAt = time.Now()
		if s.tenantClient != nil && s.clusterRepo != nil {
			cluster, err := s.clusterRepo.GetByID(ctx, binding.ClusterID)
			if err != nil {
				if errors.Is(err, entity.ErrClusterNotFound) {
					continue
				}
				return err
			}
			tenantBinding := entity.NewTenantBinding(binding.Namespace)
			tenantBinding.ServiceAccountName = binding.ServiceAccount
			tenantBinding.ResourceQuotaHard = bindingQuotaHard(binding)
			if err := s.tenantClient.EnsureTenant(ctx, cluster, tenantBinding); err != nil {
				return err
			}
		}
		if err := s.bindingRepo.Upsert(ctx, binding); err != nil {
			return err
		}
	}
	return nil
}

func (s *AuthService) DeleteUser(ctx context.Context, userID string) error {
	principal, err := authz.RequirePrincipal(ctx)
	if err != nil {
		return entity.ErrUnauthorized
	}
	if !principal.IsAdmin() {
		return entity.ErrForbidden
	}
	if userID == principal.UserID {
		return entity.ErrForbidden
	}
	user, err := s.userRepo.GetByID(ctx, userID)
	if err != nil {
		return entity.ErrUserNotFound
	}
	if err := s.ensureUserHasNoInstances(ctx, user); err != nil {
		return err
	}
	if s.isExclusiveUserWorkspace(ctx, user) {
		if err := s.cleanupUserWorkspace(ctx, user.WorkspaceID); err != nil {
			return err
		}
	}
	return s.userRepo.Delete(ctx, userID)
}

func (s *AuthService) ensureUserHasNoInstances(ctx context.Context, user *entity.User) error {
	if s.instanceRepo == nil || user == nil {
		return nil
	}
	instances, err := s.instanceRepo.List(ctx)
	if err != nil {
		return err
	}
	for _, instance := range instances {
		if instance == nil {
			continue
		}
		if instance.OwnerID == user.ID {
			return entity.ErrUserHasInstances
		}
		if user.WorkspaceID != "" && user.WorkspaceID != entity.DefaultWorkspaceID && instance.WorkspaceID == user.WorkspaceID {
			return entity.ErrUserHasInstances
		}
	}
	return nil
}

func (s *AuthService) isExclusiveUserWorkspace(ctx context.Context, user *entity.User) bool {
	if user == nil || user.Role == authz.RoleAdmin || user.WorkspaceID == "" || user.WorkspaceID == entity.DefaultWorkspaceID {
		return false
	}
	users, err := s.userRepo.List(ctx)
	if err != nil {
		return false
	}
	for _, other := range users {
		if other == nil || other.ID == user.ID {
			continue
		}
		if other.WorkspaceID == user.WorkspaceID {
			return false
		}
	}
	return true
}

func (s *AuthService) cleanupUserWorkspace(ctx context.Context, workspaceID string) error {
	if s.workspaceRepo == nil || s.bindingRepo == nil {
		return nil
	}
	workspace, err := s.workspaceRepo.GetByID(ctx, workspaceID)
	if err != nil {
		return err
	}
	if isProtectedWorkspaceNamespace(workspace.K8sNamespace) {
		return entity.ErrProtectedNamespace
	}
	bindings, err := s.bindingRepo.ListByWorkspace(ctx, workspace.ID)
	if err != nil {
		return err
	}
	for _, binding := range bindings {
		if binding == nil {
			continue
		}
		if isProtectedWorkspaceNamespace(binding.Namespace) {
			return entity.ErrProtectedNamespace
		}
		if s.tenantClient != nil && s.clusterRepo != nil {
			cluster, err := s.clusterRepo.GetByID(ctx, binding.ClusterID)
			if err != nil && !errors.Is(err, entity.ErrClusterNotFound) {
				return err
			}
			if err == nil {
				tenantBinding := entity.NewTenantBinding(binding.Namespace)
				tenantBinding.ServiceAccountName = binding.ServiceAccount
				tenantBinding.ResourceQuotaHard = resourceQuotaHard(workspace)
				if err := s.tenantClient.DeleteTenant(ctx, cluster, tenantBinding); err != nil {
					return err
				}
			}
		}
		if err := s.bindingRepo.Delete(ctx, binding.WorkspaceID, binding.ClusterID); err != nil {
			return err
		}
	}
	if err := s.workspaceRepo.Delete(ctx, workspace.ID); err != nil && !errors.Is(err, entity.ErrWorkspaceNotFound) {
		return err
	}
	return nil
}

func isProtectedWorkspaceNamespace(namespace string) bool {
	switch strings.TrimSpace(namespace) {
	case "", "default", "kube-system", "kube-public", "kube-node-lease":
		return true
	default:
		return false
	}
}

func normalizeUserRole(role string) string {
	if role == authz.RoleAdmin {
		return authz.RoleAdmin
	}
	return authz.RoleUser
}

// Login 用户登录
func (s *AuthService) Login(ctx context.Context, username, password string) (accessToken, refreshToken string, user *entity.User, err error) {
	// 查找用户
	user, err = s.userRepo.GetByUsername(ctx, username)
	if err != nil {
		return "", "", nil, entity.ErrUserNotFound
	}
	if !user.IsActive {
		return "", "", nil, entity.ErrUserInactive
	}
	if err := s.ensureWorkspaceActive(ctx, user); err != nil {
		return "", "", nil, err
	}

	// 验证密码
	if err := s.passwordHasher.Verify(password, user.PasswordHash); err != nil {
		return "", "", nil, entity.ErrInvalidPassword
	}

	// 生成 Token
	accessToken, refreshToken, err = s.tokenGenerator.Generate(user.ID, user.Username, user.Role, user.WorkspaceID)
	if err != nil {
		return "", "", nil, err
	}

	return accessToken, refreshToken, user, nil
}

// RefreshToken 刷新 Token
func (s *AuthService) RefreshToken(ctx context.Context, refreshToken string) (string, *entity.User, error) {
	claims, err := s.tokenGenerator.VerifyRefresh(refreshToken)
	if err != nil {
		return "", nil, err
	}
	user, err := s.userRepo.GetByID(ctx, claims.UserID)
	if err != nil {
		return "", nil, entity.ErrUserNotFound
	}
	if !user.IsActive {
		return "", nil, entity.ErrUserInactive
	}
	if claims.IssuedAt == nil || claims.IssuedAt.Unix() < user.RevokedAfter.Unix() {
		return "", nil, entity.ErrTokenRevoked
	}
	if err := s.ensureWorkspaceActive(ctx, user); err != nil {
		return "", nil, err
	}
	accessToken, _, err := s.tokenGenerator.Generate(user.ID, user.Username, user.Role, user.WorkspaceID)
	if err != nil {
		return "", nil, err
	}
	return accessToken, user, nil
}

// GetUserByID 根据 ID 获取用户
func (s *AuthService) GetUserByID(ctx context.Context, id string) (*entity.User, error) {
	return s.userRepo.GetByID(ctx, id)
}

// VerifyAccessToken 验证 Access Token（包括 revoked_after 检查）
func (s *AuthService) VerifyAccessToken(ctx context.Context, token string) (*authz.Principal, error) {
	// 1. JWT 自验证
	claims, err := s.tokenGenerator.VerifyAccess(token)
	if err != nil {
		return nil, err
	}

	// 2. 检查用户级别的撤销时间
	user, err := s.userRepo.GetByID(ctx, claims.UserID)
	if err != nil {
		return nil, entity.ErrUserNotFound
	}
	if !user.IsActive {
		return nil, entity.ErrUserInactive
	}

	// 3. 如果 Token 签发时间早于 revoked_after，则失效
	if claims.IssuedAt == nil || claims.IssuedAt.Unix() < user.RevokedAfter.Unix() {
		return nil, entity.ErrTokenRevoked
	}
	if err := s.ensureWorkspaceActive(ctx, user); err != nil {
		return nil, err
	}
	workspaceName := ""
	namespace := ""
	defaultClusterID := ""
	quotaCPU := ""
	quotaMemory := ""
	quotaGPU := ""
	quotaGPUMem := ""
	if s.workspaceRepo != nil && user.WorkspaceID != "" {
		if workspace, err := s.workspaceRepo.GetByID(ctx, user.WorkspaceID); err == nil && workspace != nil {
			workspaceName = workspace.Name
			namespace = workspace.K8sNamespace
			defaultClusterID = workspace.DefaultClusterID
			quotaCPU = workspace.QuotaCPU
			quotaMemory = workspace.QuotaMemory
			quotaGPU = workspace.QuotaGPU
			quotaGPUMem = workspace.QuotaGPUMem
		}
	}

	return &authz.Principal{
		UserID:            user.ID,
		Username:          user.Username,
		Role:              user.Role,
		WorkspaceID:       user.WorkspaceID,
		WorkspaceName:     workspaceName,
		Namespace:         namespace,
		DefaultClusterID:  defaultClusterID,
		QuotaCPU:          quotaCPU,
		QuotaMemory:       quotaMemory,
		QuotaGPU:          quotaGPU,
		QuotaGPUMem:       quotaGPUMem,
		Permissions:       authz.PermissionsForRole(user.Role),
		PermissionVersion: 1,
	}, nil
}

func (s *AuthService) GetWorkspaceByID(ctx context.Context, id string) (*entity.Workspace, error) {
	if s.workspaceRepo == nil || id == "" {
		return nil, entity.ErrWorkspaceNotFound
	}
	return s.workspaceRepo.GetByID(ctx, id)
}

func (s *AuthService) ensureWorkspaceActive(ctx context.Context, user *entity.User) error {
	if user.Role == authz.RoleAdmin || user.WorkspaceID == "" || s.workspaceRepo == nil {
		return nil
	}
	workspace, err := s.workspaceRepo.GetByID(ctx, user.WorkspaceID)
	if err != nil {
		return entity.ErrWorkspaceNotFound
	}
	if workspace.Status == entity.WorkspaceSuspended {
		return entity.ErrWorkspaceSuspended
	}
	return nil
}

// ChangePassword 修改密码（会触发全局登出）
func (s *AuthService) ChangePassword(ctx context.Context, userID, oldPassword, newPassword string) error {
	// 1. 获取用户
	user, err := s.userRepo.GetByID(ctx, userID)
	if err != nil {
		return entity.ErrUserNotFound
	}

	// 2. 验证旧密码
	if err := s.passwordHasher.Verify(oldPassword, user.PasswordHash); err != nil {
		return entity.ErrInvalidPassword
	}

	// 3. 哈希新密码
	newPasswordHash, err := s.passwordHasher.Hash(newPassword)
	if err != nil {
		return err
	}

	// 4. 更新密码（会自动触发 revoked_after 更新）
	user.UpdatePassword(newPasswordHash)

	// 5. 保存到数据库
	return s.userRepo.Update(ctx, user)
}

// ForceLogoutAll 强制全局登出（管理员操作）
func (s *AuthService) ForceLogoutAll(ctx context.Context, userID string) error {
	user, err := s.userRepo.GetByID(ctx, userID)
	if err != nil {
		return entity.ErrUserNotFound
	}

	user.RevokeAllTokens()
	return s.userRepo.Update(ctx, user)
}
