package com.zioinfo.esn.auth;

import com.zioinfo.esn.admin.AuditService;
import com.zioinfo.esn.auth.mapper.UserAuthMapper;
import com.zioinfo.esn.uiws.auth.OtpAuthService;
import com.zioinfo.esn.uiws.auth.TwoFactorService;
import com.zioinfo.esn.uiws.common.UiwsApiException;
import com.zioinfo.esn.uiws.common.UiwsErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Map;

/**
 * ESN 인증 서비스.
 * - 기존 단일 JWT 로그인 보존(2FA off 또는 미설정 시 회귀 0).
 * - UIWS 2FA 레이어 추가. 로그인 2단계 우선순위: OTP(TOTP) > 이메일코드 > 단일 로그인.
 *   실패 누적 max-login-fail 회 시 계정 잠금.
 */
@Service
@RequiredArgsConstructor
public class AuthService {

    private final UserAuthMapper userMapper;
    private final PasswordEncoder passwordEncoder;
    private final JwtUtil jwtUtil;
    private final TwoFactorService twoFactorService;
    private final OtpAuthService otpAuthService;
    private final AuditService auditService;

    /**
     * 1차 로그인. 2FA 활성 시 verify-token + 이메일코드 흐름으로 분기,
     * 비활성 시 기존처럼 access 토큰 즉시 발급.
     *
     * @return 2FA off: { token, type, twofa:"false" }
     *         2FA on : { verifyToken, step:"EMAIL", maskedEmail, twofa:"true" }
     */
    public Map<String, String> login(String username, String password) {
        EsnUser user = userMapper.findByUsername(username);

        // 잠금 우선 차단(존재하는 계정에 한해 잠금 메시지 — 존재 여부 누설 최소화)
        if (user != null && twoFactorService.isLocked(user)) {
            throw new UiwsApiException(UiwsErrorCode.ACCOUNT_LOCKED);
        }
        if (user == null || !user.isActive()) {
            throw new RuntimeException("ERR-AUTH-001: 존재하지 않거나 비활성 계정");
        }
        if (!passwordEncoder.matches(password, user.getPasswordHash())) {
            // 2FA(OTP 또는 이메일) 활성 시 실패 누적/잠금. 비활성 시 기존 동작(메시지만) 유지.
            if (otpAuthService.isEnabled() || twoFactorService.isEnabled()) {
                twoFactorService.recordLoginFailure(username);
                EsnUser after = userMapper.findByUsername(username);
                if (after != null && Boolean.TRUE.equals(after.getLocked())) {
                    throw new UiwsApiException(UiwsErrorCode.ACCOUNT_LOCKED);
                }
            }
            throw new RuntimeException("ERR-AUTH-002: 비밀번호 불일치");
        }

        // 비밀번호 검증 통과 — 2단계 우선순위: OTP > 이메일코드 > 단일 로그인
        if (otpAuthService.isEnabled()) {
            // { twofa:true, verifyToken, verifyMethod:(OTP|OTP_SETUP), secret?, otpAuthUri?, qrImage? }
            return otpAuthService.beginOtp(user);
        }
        if (twoFactorService.isEnabled()) {
            Map<String, String> step1 = twoFactorService.beginTwoFactor(user);
            return Map.of(
                    "twofa", "true",
                    "verifyToken", step1.get("verifyToken"),
                    "step", step1.get("step"),
                    "maskedEmail", step1.getOrDefault("maskedEmail", ""));
        }

        // 2FA 비활성 — 기존 단일 로그인 흐름(회귀 0)
        userMapper.resetLoginFail(username);
        userMapper.updateLastLogin(username);
        String token = jwtUtil.generate(username, user.getRole(), user.getTenantCode());
        return Map.of("twofa", "false", "token", token, "type", "Bearer");
    }

    public Map<String, String> me(String token) {
        return Map.of(
            "username", jwtUtil.getUsername(token),
            "role", jwtUtil.getRole(token),
            "tenant", jwtUtil.getTenant(token) != null ? jwtUtil.getTenant(token) : ""
        );
    }

    /**
     * 마이페이지 비밀번호 변경(현재 비밀번호 검증 + BCrypt 저장). UIWS changePassword 미러.
     * 현재 비번 불일치 → PASSWORD_MISMATCH, 기존과 동일 → PASSWORD_SAME_AS_OLD.
     * 새 비밀번호 평문은 로그/응답/감사에 절대 기록하지 않는다(보안 불변규칙).
     */
    @Transactional
    public void changePassword(String username, ChangePasswordRequest req) {
        EsnUser user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UiwsApiException(UiwsErrorCode.USER_NOT_FOUND);
        }
        if (!passwordEncoder.matches(req.currentPassword(), user.getPasswordHash())) {
            throw new UiwsApiException(UiwsErrorCode.PASSWORD_MISMATCH);
        }
        if (passwordEncoder.matches(req.newPassword(), user.getPasswordHash())) {
            throw new UiwsApiException(UiwsErrorCode.PASSWORD_SAME_AS_OLD);
        }
        userMapper.updatePasswordHash(username, passwordEncoder.encode(req.newPassword()));
        auditService.log("PASSWORD_CHANGE", "USER", username, "본인 비밀번호 변경");
    }
}
