package com.zioinfo.kintex.security;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.zioinfo.kintex.system.loginhistory.LoginHistoryService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.OffsetDateTime;
import java.util.Map;

/**
 * 로그인 실패 잠금 정책 — 연속 실패 임계 초과 시 {@code locked_until} 설정(계약 §5B-3).
 * 실패 메시지는 계정 열거를 막기 위해 일반화한다.
 * 부가로 로그인 이력(성공/실패)을 best-effort 기록한다(이력 실패가 인증을 막지 않음).
 */
@Service
public class LoginAttemptService {

    private final AccountSecurityMapper mapper;
    private final SecurityPolicyProperties props;
    private final LoginHistoryService loginHistory;

    public LoginAttemptService(AccountSecurityMapper mapper, SecurityPolicyProperties props,
                               LoginHistoryService loginHistory) {
        this.mapper = mapper;
        this.props = props;
        this.loginHistory = loginHistory;
    }

    /** 현재 잠금 상태면 423(LOCKED). authRow 는 findAuthSecurityByEmail 결과. */
    public void assertNotLocked(Map<String, Object> authRow) {
        if (authRow == null) {
            return; // 존재 여부 노출 방지 — 자격검증 단계에서 일반화 처리.
        }
        Object lu = authRow.get("lockedUntil");
        if (lu instanceof OffsetDateTime until && until.isAfter(OffsetDateTime.now())) {
            throw new ApiException(ErrorCode.ACCOUNT_LOCKED,
                    "로그인 시도가 일시적으로 제한되었습니다. 잠시 후 다시 시도해 주세요.");
        }
    }

    /** 자격 실패 기록 — 임계 초과 시 잠금. */
    public void recordFailure(String email, Map<String, Object> authRow) {
        // 이력은 계정 존재 여부와 무관하게 기록(마스킹 이메일만) — 침해 탐지 가치.
        loginHistory.recordFailure(email, "BAD_CREDENTIAL", clientIp(), userAgent());
        if (authRow == null) {
            return; // 미존재 계정은 상태 변경 없음.
        }
        mapper.incrementFailure(email);
        int failed = toInt(authRow.get("failedCount")) + 1;
        if (failed >= props.getLockout().getMaxFailedAttempts()) {
            mapper.lockUntil(email, OffsetDateTime.now().plusMinutes(props.getLockout().getLockMinutes()));
        }
    }

    /** 로그인 성공 — 실패 카운트/잠금 해제. */
    public void recordSuccess(String userId) {
        mapper.clearLoginState(userId);
        loginHistory.recordSuccess(userId, null, clientIp(), userAgent());
    }

    /** 현재 요청의 클라이언트 IP(X-Forwarded-For 우선). 요청 컨텍스트 없으면 null. */
    private static String clientIp() {
        HttpServletRequest req = currentRequest();
        if (req == null) {
            return null;
        }
        String xff = req.getHeader("X-Forwarded-For");
        if (xff != null && !xff.isBlank()) {
            return xff.split(",")[0].trim();
        }
        return req.getRemoteAddr();
    }

    private static String userAgent() {
        HttpServletRequest req = currentRequest();
        return req == null ? null : req.getHeader("User-Agent");
    }

    private static HttpServletRequest currentRequest() {
        if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
            return attrs.getRequest();
        }
        return null;
    }

    private static int toInt(Object o) {
        if (o instanceof Number n) {
            return n.intValue();
        }
        try {
            return o == null ? 0 : Integer.parseInt(String.valueOf(o));
        } catch (NumberFormatException e) {
            return 0;
        }
    }
}
