package service

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

	"github.com/user-management-system/internal/auth"
	"github.com/user-management-system/internal/domain"
)

func (s *AuthService) SetEmailActivationService(svc *EmailActivationService) {
	s.emailActivationSvc = svc
}

func (s *AuthService) SetEmailCodeService(svc *EmailCodeService) {
	s.emailCodeSvc = svc
}

// HasEmailCodeService 判断邮箱验证码登录服务是否已配置
func (s *AuthService) HasEmailCodeService() bool {
	return s != nil && s.emailCodeSvc != nil
}

func (s *AuthService) RegisterWithActivation(ctx context.Context, req *RegisterRequest) (*UserInfo, error) {
	if err := s.validatePassword(req.Password); err != nil {
		return nil, err
	}
	if err := s.verifyPhoneRegistration(ctx, req); err != nil {
		return nil, err
	}

	exists, err := s.userRepo.ExistsByUsername(ctx, req.Username)
	if err != nil {
		return nil, err
	}
	if exists {
		return nil, errors.New("username already exists")
	}

	if req.Email != "" {
		exists, err = s.userRepo.ExistsByEmail(ctx, req.Email)
		if err != nil {
			return nil, err
		}
		if exists {
			return nil, errors.New("email already exists")
		}
	}

	if req.Phone != "" {
		exists, err = s.userRepo.ExistsByPhone(ctx, req.Phone)
		if err != nil {
			return nil, err
		}
		if exists {
			return nil, errors.New("phone already exists")
		}
	}

	hashedPassword, err := auth.HashPassword(req.Password)
	if err != nil {
		return nil, err
	}

	initialStatus := domain.UserStatusActive
	if s.emailActivationSvc != nil && req.Email != "" {
		initialStatus = domain.UserStatusInactive
	}

	user := &domain.User{
		Username: req.Username,
		Email:    domain.StrPtr(req.Email),
		Phone:    domain.StrPtr(req.Phone),
		Password: hashedPassword,
		Nickname: req.Nickname,
		Status:   initialStatus,
	}
	if err := s.userRepo.Create(ctx, user); err != nil {
		return nil, err
	}

	s.bestEffortAssignDefaultRoles(ctx, user.ID, "register_with_activation")

	if s.emailActivationSvc != nil && req.Email != "" {
		nickname := req.Nickname
		if nickname == "" {
			nickname = req.Username
		}
		// #nosec G118 - 使用独立上下文避免请求结束后被取消
		go func() { // #nosec G118
			bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
			defer cancel()
			if err := s.emailActivationSvc.SendActivationEmail(bgCtx, user.ID, req.Email, nickname); err != nil {
				log.Printf("auth: send activation email failed, user_id=%d email=%s err=%v", user.ID, req.Email, err)
			}
		}()
	}

	userInfo := s.buildUserInfo(user)
	s.publishEvent(ctx, domain.EventUserRegistered, userInfo)
	return userInfo, nil
}

func (s *AuthService) ActivateEmail(ctx context.Context, token string) error {
	if s.emailActivationSvc == nil {
		return errors.New("email activation service is not configured")
	}

	userID, err := s.emailActivationSvc.ValidateActivationToken(ctx, token)
	if err != nil {
		return err
	}

	user, err := s.userRepo.GetByID(ctx, userID)
	if err != nil {
		return fmt.Errorf("user not found: %w", err)
	}

	if user.Status == domain.UserStatusActive {
		return errors.New("account already activated")
	}
	if user.Status != domain.UserStatusInactive {
		return errors.New("account status does not allow activation")
	}

	return s.userRepo.UpdateStatus(ctx, userID, domain.UserStatusActive)
}

func (s *AuthService) ResendActivationEmail(ctx context.Context, email string) error {
	if s.emailActivationSvc == nil {
		return errors.New("email activation service is not configured")
	}

	user, err := s.userRepo.GetByEmail(ctx, email)
	if err != nil {
		if isUserNotFoundError(err) {
			return nil
		}
		return err
	}
	if user.Status == domain.UserStatusActive {
		return nil
	}
	if user.Status != domain.UserStatusInactive {
		return errors.New("account status does not allow activation")
	}

	nickname := user.Nickname
	if nickname == "" {
		nickname = user.Username
	}
	return s.emailActivationSvc.SendActivationEmail(ctx, user.ID, email, nickname)
}

func (s *AuthService) SendEmailLoginCode(ctx context.Context, email string) error {
	if s.emailCodeSvc == nil {
		return errors.New("email code service is not configured")
	}

	_, err := s.userRepo.GetByEmail(ctx, email)
	if err != nil {
		if isUserNotFoundError(err) {
			return nil
		}
		return err
	}
	return s.emailCodeSvc.SendEmailCode(ctx, email, "login")
}

func (s *AuthService) LoginByEmailCode(ctx context.Context, email, code, ip string) (*LoginResponse, error) {
	if s.emailCodeSvc == nil {
		return nil, errors.New("email code login is disabled")
	}

	if err := s.emailCodeSvc.VerifyEmailCode(ctx, email, "login", code); err != nil {
		s.writeLoginLog(ctx, nil, domain.LoginTypeEmailCode, ip, false, err.Error())
		return nil, err
	}

	user, err := s.userRepo.GetByEmail(ctx, email)
	if err != nil {
		if isUserNotFoundError(err) {
			s.writeLoginLog(ctx, nil, domain.LoginTypeEmailCode, ip, false, "email not registered")
			return nil, errors.New("email not registered")
		}
		s.writeLoginLog(ctx, nil, domain.LoginTypeEmailCode, ip, false, err.Error())
		return nil, err
	}

	if err := s.ensureUserActive(user); err != nil {
		s.writeLoginLog(ctx, &user.ID, domain.LoginTypeEmailCode, ip, false, err.Error())
		s.recordLoginAnomaly(ctx, &user.ID, ip, "", "", false)
		return nil, err
	}

	s.bestEffortUpdateLastLogin(ctx, user.ID, ip, "email_code")
	s.writeLoginLog(ctx, &user.ID, domain.LoginTypeEmailCode, ip, true, "")
	s.recordLoginAnomaly(ctx, &user.ID, ip, "", "", true)
	s.publishEvent(ctx, domain.EventUserLogin, map[string]interface{}{
		"user_id":  user.ID,
		"username": user.Username,
		"ip":       ip,
		"method":   "email_code",
	})

	return s.generateLoginResponseWithoutRemember(ctx, user)
}
