package com.huimv.employment.service.auth;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.huimv.employment.common.exception.BizException;
import com.huimv.employment.common.exception.ErrorCode;
import com.huimv.employment.common.util.*;
import com.huimv.employment.dao.entity.FeUser;
import com.huimv.employment.dao.mapper.FeUserMapper;
import com.huimv.employment.service.auth.dto.*;
import com.huimv.employment.service.config.SecurityProperties;
import com.huimv.employment.service.sms.SmsCodeService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.regex.Pattern;

/**
 * 账密登录：前端 RSA 加密传输，数据库 BCrypt 存储。
 */
@Service
public class PasswordAuthService {

    private static final String STATUS_DISABLED = "disabled";
    private static final String RESET_PASSWORD_DENIED_MSG =
            "无法重置密码，请确认手机号与验证码，或使用验证码登录后在个人中心设置";
    private static final Pattern MOBILE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");

    private final FeUserMapper feUserMapper;
    private final JwtTokenService jwtTokenService;
    private final SecurityProperties securityProperties;
    private final SmsCodeService smsCodeService;

    public PasswordAuthService(FeUserMapper feUserMapper,
                               JwtTokenService jwtTokenService,
                               SecurityProperties securityProperties,
                               SmsCodeService smsCodeService) {
        this.feUserMapper = feUserMapper;
        this.jwtTokenService = jwtTokenService;
        this.securityProperties = securityProperties;
        this.smsCodeService = smsCodeService;
    }

    public PasswordPublicKeyResponse getPublicKey() {
        String publicKey = securityProperties.getPasswordRsaPublicKey();
        if (!StringUtils.hasText(publicKey)) {
            throw new BizException(ErrorCode.INTERNAL_ERROR, "RSA 公钥未配置");
        }
        return new PasswordPublicKeyResponse(publicKey.trim());
    }

    @Transactional(rollbackFor = Exception.class)
    public LoginResponse loginByPassword(PasswordLoginRequest request) {
        FeUser user = findUserByAccount(request.getAccount().trim());
        if (user == null) {
            throw new BizException(ErrorCode.LOGIN_BAD_CREDENTIALS);
        }
        ensureUserActive(user);
        ensurePasswordLoginAllowed(user);
        String rawPassword = decryptPassword(request.getEncryptedPassword());
        if (!PasswordUtils.matches(rawPassword, user.getPasswordHash())) {
            recordPasswordFailure(user);
            throw new BizException(ErrorCode.LOGIN_BAD_CREDENTIALS);
        }
        clearPasswordFailure(user);
        touchLogin(user);
        return buildLoginResponse(user);
    }

    @Transactional(rollbackFor = Exception.class)
    public void setPassword(Long userId, PasswordSetRequest request) {
        FeUser user = feUserMapper.selectById(userId);
        if (user == null) {
            throw new BizException(ErrorCode.UNAUTHORIZED);
        }
        ensureUserActive(user);
        String rawPassword = decryptPassword(request.getEncryptedPassword());
        String rawConfirm = decryptPassword(request.getEncryptedConfirmPassword());
        PasswordPolicyUtils.validateConfirm(rawPassword, rawConfirm);
        String mobilePlain = decryptMobileIfPresent(user);
        PasswordPolicyUtils.validate(rawPassword, mobilePlain);

        boolean firstSet = !StringUtils.hasText(user.getPasswordHash());
        if (firstSet) {
            if (!StringUtils.hasText(request.getUsername())) {
                throw new BizException(ErrorCode.BAD_REQUEST, "请设置用户名");
            }
            String username = UsernameUtils.normalize(request.getUsername());
            UsernameUtils.validate(username);
            ensureUsernameAvailable(username, userId);
            user.setUsername(username);
        } else {
            if (!StringUtils.hasText(request.getEncryptedOldPassword())) {
                throw new BizException(ErrorCode.BAD_REQUEST, "请提供原密码");
            }
            String oldRaw = decryptPassword(request.getEncryptedOldPassword());
            if (!PasswordUtils.matches(oldRaw, user.getPasswordHash())) {
                throw new BizException(ErrorCode.LOGIN_BAD_CREDENTIALS, "原密码错误");
            }
        }

        LocalDateTime now = LocalDateTime.now();
        user.setPasswordHash(PasswordUtils.encode(rawPassword));
        user.setPasswordSetAt(now);
        user.setPwdFailedCount(0);
        user.setPwdLockedUntil(null);
        user.setUpdateTime(now);
        feUserMapper.updateById(user);
    }

    @Transactional(rollbackFor = Exception.class)
    public void resetPassword(PasswordResetRequest request) {
        smsCodeService.verifyLoginCode(request.getMobile(), request.getCode());
        String mobileEnc = AesUtils.encrypt(request.getMobile(), securityProperties.getMobileAesKey());
        FeUser user = feUserMapper.selectOne(new LambdaQueryWrapper<FeUser>()
                .eq(FeUser::getMobileEnc, mobileEnc)
                .last("LIMIT 1"));
        if (user == null || !StringUtils.hasText(user.getPasswordHash())) {
            throw new BizException(ErrorCode.BAD_REQUEST, RESET_PASSWORD_DENIED_MSG);
        }
        ensureUserActive(user);

        String rawPassword = decryptPassword(request.getEncryptedPassword());
        String rawConfirm = decryptPassword(request.getEncryptedConfirmPassword());
        PasswordPolicyUtils.validateConfirm(rawPassword, rawConfirm);
        PasswordPolicyUtils.validate(rawPassword, request.getMobile());

        LocalDateTime now = LocalDateTime.now();
        user.setPasswordHash(PasswordUtils.encode(rawPassword));
        user.setPasswordSetAt(now);
        user.setPwdFailedCount(0);
        user.setPwdLockedUntil(null);
        user.setUpdateTime(now);
        feUserMapper.updateById(user);
    }

    public PasswordStatusResponse getPasswordStatus(Long userId) {
        FeUser user = feUserMapper.selectById(userId);
        if (user == null) {
            throw new BizException(ErrorCode.UNAUTHORIZED);
        }
        PasswordStatusResponse response = new PasswordStatusResponse();
        boolean set = StringUtils.hasText(user.getPasswordHash());
        response.setPasswordSet(set);
        if (set && StringUtils.hasText(user.getUsername())) {
            response.setUsernameMask(UsernameUtils.mask(user.getUsername()));
        }
        return response;
    }

    private FeUser findUserByAccount(String account) {
        if (MOBILE_PATTERN.matcher(account).matches()) {
            String mobileEnc = AesUtils.encrypt(account, securityProperties.getMobileAesKey());
            return feUserMapper.selectOne(new LambdaQueryWrapper<FeUser>()
                    .eq(FeUser::getMobileEnc, mobileEnc)
                    .last("LIMIT 1"));
        }
        String username = UsernameUtils.normalize(account);
        return feUserMapper.selectOne(new LambdaQueryWrapper<FeUser>()
                .eq(FeUser::getUsername, username)
                .last("LIMIT 1"));
    }

    private void ensurePasswordLoginAllowed(FeUser user) {
        if (!StringUtils.hasText(user.getPasswordHash())) {
            throw new BizException(ErrorCode.PASSWORD_NOT_SET);
        }
        if (user.getPwdLockedUntil() != null && user.getPwdLockedUntil().isAfter(LocalDateTime.now())) {
            throw new BizException(ErrorCode.LOGIN_LOCKED);
        }
    }

    private void recordPasswordFailure(FeUser user) {
        int count = user.getPwdFailedCount() == null ? 0 : user.getPwdFailedCount();
        count++;
        user.setPwdFailedCount(count);
        if (count >= securityProperties.getPasswordMaxFailures()) {
            user.setPwdLockedUntil(LocalDateTime.now().plusMinutes(securityProperties.getPasswordLockMinutes()));
        }
        user.setUpdateTime(LocalDateTime.now());
        feUserMapper.updateById(user);
    }

    private void clearPasswordFailure(FeUser user) {
        user.setPwdFailedCount(0);
        user.setPwdLockedUntil(null);
    }

    private void touchLogin(FeUser user) {
        LocalDateTime now = LocalDateTime.now();
        user.setLastLoginAt(now);
        user.setUpdateTime(now);
        feUserMapper.updateById(user);
    }

    private void ensureUsernameAvailable(String username, Long currentUserId) {
        FeUser existing = feUserMapper.selectOne(new LambdaQueryWrapper<FeUser>()
                .eq(FeUser::getUsername, username)
                .last("LIMIT 1"));
        if (existing != null && !existing.getId().equals(currentUserId)) {
            throw new BizException(ErrorCode.USERNAME_ALREADY_EXISTS);
        }
    }

    private String decryptPassword(String encrypted) {
        return RsaUtils.decryptByPrivateKey(encrypted, securityProperties.getPasswordRsaPrivateKey());
    }

    private String decryptMobileIfPresent(FeUser user) {
        if (!StringUtils.hasText(user.getMobileEnc())) {
            return null;
        }
        try {
            return AesUtils.decrypt(user.getMobileEnc(), securityProperties.getMobileAesKey());
        } catch (Exception ex) {
            return null;
        }
    }

    private void ensureUserActive(FeUser user) {
        if (STATUS_DISABLED.equals(user.getStatus())) {
            throw new BizException(ErrorCode.USER_DISABLED);
        }
    }

    private LoginResponse buildLoginResponse(FeUser user) {
        JwtTokenService.TokenIssueResult token = jwtTokenService.issueToken(user.getId(), user.getUserType());
        LoginResponse response = new LoginResponse();
        response.setToken(token.getToken());
        response.setExpiresIn(token.getExpiresIn());
        response.setUserId(user.getId());
        response.setUserType(user.getUserType());
        response.setMobileMask(user.getMobileMask());
        response.setNickname(user.getNickname());
        response.setPasswordSet(StringUtils.hasText(user.getPasswordHash()));
        return response;
    }
}
