package service

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"backupx/server/internal/apperror"
	"backupx/server/internal/model"
	"backupx/server/internal/repository"
	"backupx/server/internal/security"
)

type SetupInput struct {
	Username    string `json:"username" binding:"required,min=3,max=64"`
	Password    string `json:"password" binding:"required,min=8,max=128"`
	DisplayName string `json:"displayName" binding:"required,min=1,max=128"`
}

type LoginInput struct {
	Username string `json:"username" binding:"required,min=3,max=64"`
	Password string `json:"password" binding:"required,min=8,max=128"`
}

type AuthPayload struct {
	Token string      `json:"token"`
	User  *UserOutput `json:"user"`
}

type UserOutput struct {
	ID          uint   `json:"id"`
	Username    string `json:"username"`
	DisplayName string `json:"displayName"`
	Role        string `json:"role"`
}

type AuthService struct {
	users        repository.UserRepository
	configs      repository.SystemConfigRepository
	jwtManager   *security.JWTManager
	rateLimiter  *security.LoginRateLimiter
	auditService *AuditService
}

func NewAuthService(
	users repository.UserRepository,
	configs repository.SystemConfigRepository,
	jwtManager *security.JWTManager,
	rateLimiter *security.LoginRateLimiter,
) *AuthService {
	return &AuthService{users: users, configs: configs, jwtManager: jwtManager, rateLimiter: rateLimiter}
}

func (s *AuthService) SetAuditService(auditService *AuditService) {
	s.auditService = auditService
}

func (s *AuthService) SetupStatus(ctx context.Context) (bool, error) {
	count, err := s.users.Count(ctx)
	if err != nil {
		return false, apperror.Internal("AUTH_STATUS_FAILED", "无法检查初始化状态", err)
	}
	return count > 0, nil
}

func (s *AuthService) Setup(ctx context.Context, input SetupInput) (*AuthPayload, error) {
	initialized, err := s.SetupStatus(ctx)
	if err != nil {
		return nil, err
	}
	if initialized {
		return nil, apperror.Conflict("AUTH_SETUP_DISABLED", "系统已初始化，请直接登录", nil)
	}

	existing, err := s.users.FindByUsername(ctx, strings.TrimSpace(input.Username))
	if err != nil {
		return nil, apperror.Internal("AUTH_LOOKUP_FAILED", "无法检查账户状态", err)
	}
	if existing != nil {
		return nil, apperror.Conflict("AUTH_USERNAME_EXISTS", "用户名已存在", nil)
	}

	hash, err := security.HashPassword(input.Password)
	if err != nil {
		return nil, apperror.Internal("AUTH_HASH_FAILED", "无法处理密码", err)
	}

	user := &model.User{
		Username:     strings.TrimSpace(input.Username),
		PasswordHash: hash,
		DisplayName:  strings.TrimSpace(input.DisplayName),
		Role:         "admin",
	}
	if err := s.users.Create(ctx, user); err != nil {
		return nil, apperror.Internal("AUTH_CREATE_USER_FAILED", "无法创建管理员账户", err)
	}

	token, err := s.jwtManager.Generate(user)
	if err != nil {
		return nil, apperror.Internal("AUTH_TOKEN_FAILED", "无法生成访问令牌", err)
	}

	if s.auditService != nil {
		s.auditService.Record(AuditEntry{
			UserID: user.ID, Username: user.Username,
			Category: "auth", Action: "setup",
			TargetType: "user", TargetID: fmt.Sprintf("%d", user.ID), TargetName: user.Username,
			Detail: "系统初始化，创建管理员账户",
		})
	}

	return &AuthPayload{Token: token, User: ToUserOutput(user)}, nil
}

func (s *AuthService) Login(ctx context.Context, input LoginInput, clientKey string) (*AuthPayload, error) {
	if clientKey == "" {
		clientKey = "unknown"
	}
	if !s.rateLimiter.Allow(clientKey) {
		return nil, apperror.TooManyRequests("AUTH_RATE_LIMITED", "登录尝试过于频繁，请稍后再试", nil)
	}

	user, err := s.users.FindByUsername(ctx, strings.TrimSpace(input.Username))
	if err != nil {
		return nil, apperror.Internal("AUTH_LOOKUP_FAILED", "无法执行登录校验", err)
	}
	if user == nil {
		if s.auditService != nil {
			s.auditService.Record(AuditEntry{
				Category: "auth", Action: "login_failed",
				Detail: fmt.Sprintf("用户名不存在: %s", strings.TrimSpace(input.Username)),
				ClientIP: clientKey,
			})
		}
		return nil, apperror.Unauthorized("AUTH_INVALID_CREDENTIALS", "用户名或密码错误", nil)
	}
	if err := security.ComparePassword(user.PasswordHash, input.Password); err != nil {
		if s.auditService != nil {
			s.auditService.Record(AuditEntry{
				UserID: user.ID, Username: user.Username,
				Category: "auth", Action: "login_failed",
				Detail: "密码错误", ClientIP: clientKey,
			})
		}
		return nil, apperror.Unauthorized("AUTH_INVALID_CREDENTIALS", "用户名或密码错误", err)
	}

	s.rateLimiter.Reset(clientKey)
	token, err := s.jwtManager.Generate(user)
	if err != nil {
		return nil, apperror.Internal("AUTH_TOKEN_FAILED", "无法生成访问令牌", err)
	}

	if s.auditService != nil {
		s.auditService.Record(AuditEntry{
			UserID: user.ID, Username: user.Username,
			Category: "auth", Action: "login_success",
			Detail: "登录成功", ClientIP: clientKey,
		})
	}

	return &AuthPayload{Token: token, User: ToUserOutput(user)}, nil
}

func (s *AuthService) GetCurrentUser(ctx context.Context, subject string) (*UserOutput, error) {
	userID, err := strconv.ParseUint(subject, 10, 64)
	if err != nil {
		return nil, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效用户身份", err)
	}
	user, err := s.users.FindByID(ctx, uint(userID))
	if err != nil {
		return nil, apperror.Internal("AUTH_LOOKUP_FAILED", "无法获取当前用户", err)
	}
	if user == nil {
		return nil, apperror.Unauthorized("AUTH_USER_NOT_FOUND", "当前用户不存在", errors.New("user not found"))
	}
	return ToUserOutput(user), nil
}

type ChangePasswordInput struct {
	OldPassword string `json:"oldPassword" binding:"required,min=8,max=128"`
	NewPassword string `json:"newPassword" binding:"required,min=8,max=128"`
}

func (s *AuthService) ChangePassword(ctx context.Context, subject string, input ChangePasswordInput) error {
	userID, err := strconv.ParseUint(subject, 10, 64)
	if err != nil {
		return apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效用户身份", err)
	}
	user, err := s.users.FindByID(ctx, uint(userID))
	if err != nil {
		return apperror.Internal("AUTH_LOOKUP_FAILED", "无法获取当前用户", err)
	}
	if user == nil {
		return apperror.Unauthorized("AUTH_USER_NOT_FOUND", "当前用户不存在", errors.New("user not found"))
	}
	if err := security.ComparePassword(user.PasswordHash, input.OldPassword); err != nil {
		return apperror.BadRequest("AUTH_WRONG_PASSWORD", "旧密码不正确", err)
	}
	hash, err := security.HashPassword(input.NewPassword)
	if err != nil {
		return apperror.Internal("AUTH_HASH_FAILED", "无法处理密码", err)
	}
	user.PasswordHash = hash
	if err := s.users.Update(ctx, user); err != nil {
		return apperror.Internal("AUTH_UPDATE_FAILED", "密码修改失败", err)
	}

	if s.auditService != nil {
		s.auditService.Record(AuditEntry{
			UserID: user.ID, Username: user.Username,
			Category: "auth", Action: "change_password",
			Detail: "密码修改成功",
		})
	}

	return nil
}

func ToUserOutput(user *model.User) *UserOutput {
	if user == nil {
		return nil
	}
	return &UserOutput{
		ID:          user.ID,
		Username:    user.Username,
		DisplayName: user.DisplayName,
		Role:        user.Role,
	}
}

func SubjectFromContextValue(value any) (string, error) {
	subject, ok := value.(string)
	if !ok || strings.TrimSpace(subject) == "" {
		return "", fmt.Errorf("invalid subject context")
	}
	return subject, nil
}
