package com.zioinfo.kintex.auction;

import com.zioinfo.kintex.auction.dto.AuctionDtos.RankRow;
import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;
import java.util.List;
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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * M15 실시간 순위 브로드캐스트(WebSocket {@code /topic/auctions/{id}/ranking})의 <b>봉인 안전성</b> 회귀 테스트.
 *
 * <p>{@link AuctionRealtimeService}는 순위 변동(BID/CLOSE/AWARD) 시 {@link AuctionService#publicRanking}로
 * 만든 공개 스냅샷을 모든 구독자에게 동일 전송한다. 이 페이로드가 어떤 뷰어별 개인화 정보(내 순위 표시·타사 금액·
 * 업체 실명)도 담지 않음을 고정하여, 폴링→푸시 전환 후에도 노출 범위가 기존 공개 폴링과 동일함을 보장한다.
 */
class AuctionRealtimeRankingTest {

    private static Map<String, Object> bid(long total) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("total", total);
        m.put("companyId", "c-secret-" + total);   // 존재해도 스냅샷에 새어나가면 안 됨
        m.put("companyName", "비밀장치㈜");          // 업체 실명 — 절대 미노출
        return m;
    }

    @Test
    void broadcastSnapshot_neverMarksIsMe() {
        List<RankRow> ranking = AuctionService.publicRanking(List.of(bid(8_400_000L), bid(8_700_000L), bid(9_100_000L)));
        // 브로드캐스트는 모든 구독자 공용 → 뷰어별 'isMe' 는 항상 false 여야 한다.
        assertTrue(ranking.stream().noneMatch(RankRow::isMe), "브로드캐스트 순위는 isMe 를 담아선 안 된다(개인화 금지)");
    }

    @Test
    void broadcastSnapshot_revealsOnlyLowestPrice() {
        List<RankRow> ranking = AuctionService.publicRanking(List.of(bid(8_400_000L), bid(8_700_000L), bid(9_100_000L)));
        assertEquals(3, ranking.size());

        RankRow first = ranking.get(0);
        assertTrue(first.isLowest(), "1위는 최저가여야 한다");
        assertFalse(first.priceMasked(), "최저가만 공개(마스킹 해제)");
        assertNotNull(first.price(), "최저가 금액은 노출된다(공개 폴링과 동일)");
        assertEquals(8_400_000L, first.price());

        // 2위 이하 타사 금액은 마스킹(null) — 봉인 유지.
        for (int i = 1; i < ranking.size(); i++) {
            RankRow r = ranking.get(i);
            assertFalse(r.isLowest());
            assertTrue(r.priceMasked(), "비최저 응찰가는 마스킹되어야 한다");
            assertNull(r.price(), "타사 금액은 절대 노출되지 않는다");
        }
    }

    @Test
    void broadcastSnapshot_aliasHidesCompanyIdentity() {
        List<RankRow> ranking = AuctionService.publicRanking(List.of(bid(8_400_000L), bid(8_700_000L)));
        // alias 는 위치 라벨(순위/최저가)만 — 업체 실명·ID 파생 금지.
        assertEquals("현재 최저가", ranking.get(0).alias());
        for (RankRow r : ranking) {
            assertFalse(r.alias().contains("비밀장치"), "업체 실명이 alias 로 새어나가면 안 된다");
            assertFalse(r.alias().contains("c-secret"), "업체 ID 가 alias 로 새어나가면 안 된다");
        }
    }

    @Test
    void broadcastSnapshot_emptyRankingIsSafe() {
        List<RankRow> ranking = AuctionService.publicRanking(List.of());
        assertTrue(ranking.isEmpty(), "응찰 없음 → 빈 순위(널 안전)");
    }
}
