package com.zioinfo.kintex.visitor;

import com.zioinfo.kintex.ai.AiTextRouter;
import com.zioinfo.kintex.visitor.dto.VisitorDtos.CheckinRequest;
import com.zioinfo.kintex.visitor.dto.VisitorDtos.CheckinResult;
import com.zioinfo.kintex.visitor.scoring.RuleBasedLeadScorer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * M10 현장 체크인 중복 방지 단위테스트.
 * 첫 스캔은 done 처리(alreadyCheckedIn=false), 이미 완료된 배지의 재스캔은 재집계 없이 alreadyCheckedIn=true.
 */
class VisitorCheckinServiceTest {

    private static final String EVENT = "e-2026-smf";
    private static final String TOKEN = "QR-ABC123";
    private static final String RID = "vr-smf-006";

    private VisitorMapper mapper;
    private VisitorService service;

    @BeforeEach
    void setUp() {
        mapper = mock(VisitorMapper.class);
        service = new VisitorService(mapper, new RuleBasedLeadScorer(), mock(AiTextRouter.class));
    }

    private static Map<String, Object> row(String state, String checkinAt) {
        Map<String, Object> m = new HashMap<>();
        m.put("registrationId", RID);
        m.put("nameMasked", "한*수");
        m.put("type", "visitor");
        m.put("company", "두산로보틱스");
        m.put("badgeCode", "KTX-V-0006");
        m.put("badgeIssued", true);
        m.put("checkinState", state);
        m.put("checkinAt", checkinAt);
        return m;
    }

    @Test
    void firstScan_marksCheckedIn() {
        // 토큰 해소 → waiting. 갱신 1행. 재조회 → done.
        when(mapper.findForCheckin(eq(EVENT), eq(TOKEN), isNull())).thenReturn(row("waiting", null));
        when(mapper.markCheckedIn(EVENT, RID)).thenReturn(1);
        when(mapper.findForCheckin(eq(EVENT), isNull(), eq(RID))).thenReturn(row("done", "2026.08.11 09:30"));

        CheckinResult res = service.checkin(EVENT, new CheckinRequest(TOKEN, null));

        assertFalse(res.alreadyCheckedIn(), "첫 체크인은 중복이 아니다");
        assertEquals("done", res.checkinState());
        assertEquals("한*수", res.nameMasked()); // 마스킹 필드만 노출
        verify(mapper, times(1)).markCheckedIn(EVENT, RID);
    }

    @Test
    void duplicateScan_doesNotRecount() {
        // 이미 done → markCheckedIn 호출 금지, alreadyCheckedIn=true.
        when(mapper.findForCheckin(eq(EVENT), eq(TOKEN), isNull())).thenReturn(row("done", "2026.08.11 09:30"));
        when(mapper.findForCheckin(eq(EVENT), isNull(), eq(RID))).thenReturn(row("done", "2026.08.11 09:30"));

        CheckinResult res = service.checkin(EVENT, new CheckinRequest(TOKEN, null));

        assertTrue(res.alreadyCheckedIn(), "이미 완료된 배지 재스캔은 중복으로 표시");
        assertEquals("done", res.checkinState());
        verify(mapper, never()).markCheckedIn(EVENT, RID);
    }

    @Test
    void raceCondition_updateReturnsZero_treatedAsAlready() {
        // 조회 시엔 waiting 이었으나 동시 요청으로 UPDATE 0행 → 이미 완료로 간주(중복).
        when(mapper.findForCheckin(eq(EVENT), eq(TOKEN), isNull())).thenReturn(row("waiting", null));
        when(mapper.markCheckedIn(EVENT, RID)).thenReturn(0);
        when(mapper.findForCheckin(eq(EVENT), isNull(), eq(RID))).thenReturn(row("done", "2026.08.11 09:30"));

        CheckinResult res = service.checkin(EVENT, new CheckinRequest(TOKEN, null));

        assertTrue(res.alreadyCheckedIn());
        verify(mapper, times(1)).markCheckedIn(EVENT, RID);
    }
}
