package com.zioinfo.esn.uiws.auth;

import com.zioinfo.esn.admin.AuditService;
import com.zioinfo.esn.auth.EsnUser;
import com.zioinfo.esn.auth.JwtUtil;
import com.zioinfo.esn.auth.OtpSetupResponse;
import com.zioinfo.esn.auth.mapper.UserAuthMapper;
import com.zioinfo.esn.uiws.common.UiwsApiException;
import com.zioinfo.esn.uiws.common.UiwsErrorCode;
import com.zioinfo.esn.uiws.config.UiwsProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * TOTP(OTP 2차 인증) 오케스트레이션 레이어 — UIWS AuthService 의 OTP 경로 미러.
 *
 * <p>흐름:
 * <ol>
 *   <li>1차 로그인 성공 → {@link #beginOtp}: verify-token 발급 + (미등록이면 보류 시크릿+QR) 반환.</li>
 *   <li>{@code POST /verify-otp}(verifyToken+code) → {@link #verifyOtp}: 6자리 검증 후 access 발급.
 *       최초 로그인이면 등록 확정(otp_enabled=true).</li>
 *   <li>마이페이지: {@link #setup}/{@link #confirm}/{@link #disable}.</li>
 * </ol>
 *
 * <p>보안 불변: 시크릿·QR·otpauth URI 는 setup/OTP_SETUP 응답에서만 노출. 로그·감사에 시크릿/코드 미기록.
 * 기존 auth(JWT·RBAC) 엔진 교체 없음 — TOTP 레이어만 추가. access 는 ESN JwtUtil(3-클레임) 정책 재사용.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OtpAuthService {

    private final UserAuthMapper userMapper;
    private final JwtUtil jwtUtil;
    private final TotpService totpService;
    private final UiwsProperties properties;
    private final AuditService auditService;

    /** 로그인 2단계에 OTP 경로를 사용할지. */
    public boolean isEnabled() {
        return properties.getAuth().isOtpEnabled();
    }

    private static boolean blank(String s) {
        return s == null || s.isBlank();
    }

    /**
     * 1차 로그인 성공 후 OTP 2단계 시작.
     * @return 미등록: { twofa:true, verifyToken, verifyMethod:OTP_SETUP, secret, otpAuthUri, qrImage }
     *         등록됨: { twofa:true, verifyToken, verifyMethod:OTP }
     */
    @Transactional
    public Map<String, String> beginOtp(EsnUser user) {
        long tokenValidity = properties.getAuth().getVerifyTokenValiditySeconds();
        String verifyToken = jwtUtil.generateVerifyToken(user.getUsername(), tokenValidity);
        // 성공 시 실패카운트 초기화(잠금 회복)
        userMapper.resetLoginFail(user.getUsername());

        Map<String, String> resp = new LinkedHashMap<>();
        resp.put("twofa", "true");
        resp.put("verifyToken", verifyToken);

        if (blank(user.getOtpSecret())) {
            // 미등록 최초 로그인 — 보류 시크릿 발급 + QR(이 응답에서만 노출)
            String secret = totpService.generateSecret();
            userMapper.updateOtpSecret(user.getUsername(), secret);
            resp.put("verifyMethod", "OTP_SETUP");
            resp.put("secret", secret);
            resp.put("otpAuthUri", totpService.otpAuthUri(secret, user.getUsername()));
            resp.put("qrImage", totpService.qrImageDataUri(secret, user.getUsername()));
        } else {
            resp.put("verifyMethod", "OTP");
        }
        return resp;
    }

    /**
     * 2단계 검증: verify-token + 6자리 코드 → access 발급. 최초 로그인이면 등록 확정.
     * @return { token, type, username, role, tenant } (ESN 2FA verify 응답 shape 동일)
     */
    @Transactional
    public Map<String, String> verifyOtp(String verifyToken, String code) {
        String username = jwtUtil.parseVerifyTokenUsername(verifyToken);
        if (username == null) {
            throw new UiwsApiException(UiwsErrorCode.VERIFY_TOKEN_INVALID);
        }
        EsnUser user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UiwsApiException(UiwsErrorCode.VERIFY_TOKEN_INVALID);
        }
        if (Boolean.TRUE.equals(user.getLocked())) {
            throw new UiwsApiException(UiwsErrorCode.ACCOUNT_LOCKED);
        }
        if (!totpService.verify(user.getOtpSecret(), code)) {
            // OTP 오입력도 실패 카운트에 합산(정책) — 임계 도달 시 잠금.
            userMapper.incrementLoginFail(username, properties.getAuth().getMaxLoginFail());
            throw new UiwsApiException(UiwsErrorCode.VERIFY_CODE_INVALID);
        }

        // 최초 로그인(보류 시크릿) → 등록 확정(멱등)
        if (!Boolean.TRUE.equals(user.getOtpEnabled())) {
            userMapper.enableOtp(username);
            auditService.log("OTP_ENROLL", "USER", username, "최초 로그인 OTP 등록 확정");
        }
        userMapper.resetLoginFail(username);
        userMapper.updateLastLogin(username);

        String access = jwtUtil.generate(user.getUsername(), user.getRole(), user.getTenantCode());
        Map<String, String> resp = new LinkedHashMap<>();
        resp.put("token", access);
        resp.put("type", "Bearer");
        resp.put("username", user.getUsername());
        resp.put("role", user.getRole() == null ? "" : user.getRole());
        resp.put("tenant", user.getTenantCode() == null ? "" : user.getTenantCode());
        return resp;
    }

    /** 마이페이지 OTP 등록/재설정 시작: 새 보류 시크릿 발급(기존 시크릿 무효화) + QR 반환. */
    @Transactional
    public OtpSetupResponse setup(String username) {
        EsnUser user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UiwsApiException(UiwsErrorCode.USER_NOT_FOUND);
        }
        String secret = totpService.generateSecret();
        userMapper.updateOtpSecret(username, secret); // 확정 전엔 otp_enabled 유지(confirm 에서 재확정)
        auditService.log("OTP_SETUP", "USER", username, "OTP 등록/재설정 시작(새 시크릿 발급)");
        return new OtpSetupResponse(
                secret,
                totpService.otpAuthUri(secret, username),
                totpService.qrImageDataUri(secret, username));
    }

    /** 마이페이지 OTP 확인·활성화: 앱 코드 검증 성공 시 otp_enabled=true. */
    @Transactional
    public void confirm(String username, String code) {
        EsnUser user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UiwsApiException(UiwsErrorCode.USER_NOT_FOUND);
        }
        if (blank(user.getOtpSecret())) {
            throw new UiwsApiException(UiwsErrorCode.OTP_NOT_REGISTERED);
        }
        if (!totpService.verify(user.getOtpSecret(), code)) {
            throw new UiwsApiException(UiwsErrorCode.VERIFY_CODE_INVALID);
        }
        userMapper.enableOtp(username);
        auditService.log("OTP_ENABLE", "USER", username, "OTP 2차 인증 활성화");
    }

    /** 마이페이지 OTP 해제: 시크릿 폐기 + otp_enabled=false. */
    @Transactional
    public void disable(String username) {
        EsnUser user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UiwsApiException(UiwsErrorCode.USER_NOT_FOUND);
        }
        userMapper.disableOtp(username);
        auditService.log("OTP_DISABLE", "USER", username, "OTP 2차 인증 해제");
    }
}
