package com.zioinfo.kintex.security;

import org.springframework.stereotype.Component;

import java.security.SecureRandom;
import java.time.Instant;
import java.util.Base64;
import java.util.concurrent.ConcurrentHashMap;

/**
 * OTP 2단계 인증 챌린지 저장소(무상태 JWT 아키텍처의 짧은 수명 프리오스 토큰).
 * 메모리 기반·5분 TTL·1회성. 토큰→userId. (단일 노드 기준; 다중 노드 확장 시 Redis 대체 가능.)
 */
@Component
public class OtpChallengeStore {

    private static final long TTL_MILLIS = 5 * 60 * 1000L;
    /** 챌린지당 코드 검증 실패 허용 횟수 — 초과 시 챌린지 무효화(브루트포스 방어). */
    private static final int MAX_FAILURES = 5;
    private final SecureRandom random = new SecureRandom();
    private final ConcurrentHashMap<String, Entry> store = new ConcurrentHashMap<>();

    private record Entry(String userId, long expiresAt, int failures) {
    }

    /** userId에 대한 1회성 챌린지 토큰 발급. */
    public String issue(String userId) {
        purgeExpired();
        byte[] buf = new byte[24];
        random.nextBytes(buf);
        String token = Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
        store.put(token, new Entry(userId, Instant.now().toEpochMilli() + TTL_MILLIS, 0));
        return token;
    }

    /** 토큰 조회(비소비) → userId. 만료/무효면 null. 등록(enroll) setup 단계에서 사용. */
    public String peek(String token) {
        if (token == null) {
            return null;
        }
        Entry e = store.get(token);
        if (e == null || e.expiresAt() < Instant.now().toEpochMilli()) {
            return null;
        }
        return e.userId();
    }

    /** 토큰 소비(1회성) → userId. 만료/무효면 null. */
    public String consume(String token) {
        if (token == null) {
            return null;
        }
        Entry e = store.remove(token);
        if (e == null || e.expiresAt() < Instant.now().toEpochMilli()) {
            return null;
        }
        return e.userId();
    }

    /**
     * 코드 검증 실패 기록 — 챌린지는 유지(재시도 허용)하되 {@value #MAX_FAILURES}회 초과 시 무효화.
     * 반환: 챌린지가 아직 살아 있으면 true(재시도 가능), 무효화·만료면 false.
     */
    public boolean recordFailure(String token) {
        if (token == null) {
            return false;
        }
        Entry e = store.get(token);
        if (e == null || e.expiresAt() < Instant.now().toEpochMilli()) {
            store.remove(token);
            return false;
        }
        int failures = e.failures() + 1;
        if (failures >= MAX_FAILURES) {
            store.remove(token);
            return false;
        }
        store.put(token, new Entry(e.userId(), e.expiresAt(), failures));
        return true;
    }

    private void purgeExpired() {
        long now = Instant.now().toEpochMilli();
        store.entrySet().removeIf(en -> en.getValue().expiresAt() < now);
    }
}
