package dev.awesomedev.mirim.service;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 이 파일이 하는 일:
 * 로그인 잠금(무차별 대입 방어)의 규칙을 검증한다.
 *
 * 학습 포인트 — 가짜 시계(MutableClock)로 시간을 돌린다.
 * "5분 지나면 풀린다"를 검증하려고 진짜 5분을 기다릴 수는 없다.
 * LoginAttemptService가 Clock을 주입받게 설계했기 때문에,
 * 테스트에서는 우리가 바늘을 마음대로 움직이는 시계를 꽂아 넣을 수 있다.
 */
class LoginAttemptServiceTest {

    /** 테스트용 시계: advance()를 부르면 그만큼 시간이 "지나간다". */
    static class MutableClock extends Clock {
        private Instant now = Instant.parse("2026-09-01T09:00:00Z");

        void advance(Duration duration) {
            now = now.plus(duration);
        }

        @Override
        public Instant instant() {
            return now;
        }

        @Override
        public java.time.ZoneId getZone() {
            return ZoneOffset.UTC;
        }

        @Override
        public Clock withZone(java.time.ZoneId zone) {
            return this;
        }
    }

    private final MutableClock clock = new MutableClock();
    private final LoginAttemptService service = new LoginAttemptService(clock);

    @Test
    @DisplayName("5회 실패하면 잠기고, 4회까지는 잠기지 않는다")
    void locks_after_five_failures() {
        for (int i = 0; i < 4; i++) {
            service.recordFailure("mentor1", "1.2.3.4");
        }
        assertFalse(service.isLocked("mentor1", "1.2.3.4")); // 4회까지는 통과

        service.recordFailure("mentor1", "1.2.3.4");         // 5회째
        assertTrue(service.isLocked("mentor1", "1.2.3.4"));  // 잠김
    }

    @Test
    @DisplayName("잠겨도 다른 IP의 같은 아이디는 로그인할 수 있다 (잠금 악용 DoS 방지)")
    void lock_is_per_ip() {
        for (int i = 0; i < 5; i++) {
            service.recordFailure("mentor1", "6.6.6.6"); // 공격자 IP
        }
        assertTrue(service.isLocked("mentor1", "6.6.6.6"));
        assertFalse(service.isLocked("mentor1", "1.2.3.4")); // 진짜 멘토의 IP는 멀쩡
    }

    @Test
    @DisplayName("잠금은 5분이 지나면 풀린다")
    void lock_expires_after_five_minutes() {
        for (int i = 0; i < 5; i++) {
            service.recordFailure("mentor1", "1.2.3.4");
        }
        assertTrue(service.isLocked("mentor1", "1.2.3.4"));

        clock.advance(Duration.ofMinutes(5).plusSeconds(1)); // 시간을 돌린다!
        assertFalse(service.isLocked("mentor1", "1.2.3.4"));
    }

    @Test
    @DisplayName("로그인에 성공하면 실패 횟수가 초기화된다")
    void success_resets_failures() {
        for (int i = 0; i < 4; i++) {
            service.recordFailure("mentor1", "1.2.3.4");
        }
        service.reset("mentor1", "1.2.3.4"); // 로그인 성공

        service.recordFailure("mentor1", "1.2.3.4"); // 다시 1회부터
        assertFalse(service.isLocked("mentor1", "1.2.3.4"));
    }

    @Test
    @DisplayName("아이디 대소문자를 바꿔 시도해도 같은 기록으로 센다 (우회 방지)")
    void username_is_case_insensitive() {
        for (int i = 0; i < 5; i++) {
            service.recordFailure("Mentor1", "1.2.3.4");
        }
        assertTrue(service.isLocked("MENTOR1", "1.2.3.4"));
    }
}
