package com.zioinfo.kintex.auction;

import com.zioinfo.kintex.auction.dto.AuctionDtos.RankingSnapshot;
import com.zioinfo.kintex.auction.dto.AuctionDtos.RankRow;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.time.Instant;
import java.util.List;
import java.util.Map;

/**
 * M15 옥션 실시간 순위 브로드캐스트(폴링 → WebSocket 푸시 전환).
 *
 * <p>구독 토픽: {@code /topic/auctions/{auctionId}/ranking} (기존 {@code /topic/render/{jobId}} 컨벤션과 정합).
 * 렌더 완료 푸시와 동일한 STOMP 인프라({@link com.zioinfo.kintex.config.WebSocketConfig})를 재사용한다 —
 * 브로커 설정은 신설하지 않는다.
 *
 * <p><b>봉인 입찰 보안</b>: 페이로드({@link RankingSnapshot})는 모든 구독자에게 동일 전송되므로
 * {@link AuctionService#publicRanking}(뷰어별 개인화 없음·최저가만 공개)만 담는다. 개인화 순위(내 순위·금액)는
 * 클라이언트가 인증된 상세 API로 재조회하여 서버 마스킹을 그대로 적용받는다.
 *
 * <p><b>트랜잭션 경계</b>: 순위 변동 트랜잭션이 <b>커밋된 뒤에만</b> 전송한다(afterCommit). 롤백 시 미전송 —
 * 유령 갱신·구독자 재조회 레이스(커밋 전 조회)를 방지한다.
 */
@Service
public class AuctionRealtimeService {

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AuctionRealtimeService.class);

    private final SimpMessagingTemplate messaging;
    private final AuctionMapper mapper;

    public AuctionRealtimeService(SimpMessagingTemplate messaging, AuctionMapper mapper) {
        this.messaging = messaging;
        this.mapper = mapper;
    }

    /**
     * 순위 변동 이벤트를 커밋 후 브로드캐스트한다.
     *
     * @param auctionId 옥션 ID
     * @param event     BID | CLOSE | AWARD (원인 라벨)
     */
    public void broadcastRanking(String auctionId, String event) {
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    sendSafe(auctionId, event);
                }
            });
        } else {
            sendSafe(auctionId, event);
        }
    }

    /** 전송 실패는 격리(순위 갱신은 폴백 폴링이 보장) — 스택트레이스 미노출. */
    private void sendSafe(String auctionId, String event) {
        try {
            RankingSnapshot snap = buildSnapshot(auctionId, event);
            if (snap == null) {
                return;
            }
            messaging.convertAndSend("/topic/auctions/" + auctionId + "/ranking", snap);
        } catch (RuntimeException e) {
            log.warn("옥션 순위 브로드캐스트 실패(무시·폴링 폴백): auction={} reason={}",
                    auctionId, e.getClass().getSimpleName());
        }
    }

    private RankingSnapshot buildSnapshot(String auctionId, String event) {
        Map<String, Object> a = mapper.findAuction(auctionId);
        if (a == null) {
            return null;
        }
        boolean closed = bool(a.get("closed"));
        List<RankRow> ranking = AuctionService.publicRanking(mapper.listRanking(auctionId));
        return new RankingSnapshot(
                auctionId, str(a.get("eventId")), event,
                intOr(a.get("round"), 1), !closed,
                intOr(a.get("bidderCount"), 0), lng(a.get("lowestPrice")),
                ranking, Instant.now().toString());
    }

    // ── 로컬 매핑 헬퍼(AuctionService 와 동일 규칙, 무상태) ──
    private static String str(Object o) {
        return o == null ? null : String.valueOf(o);
    }

    private static boolean bool(Object o) {
        if (o instanceof Boolean b) return b;
        return o != null && Boolean.parseBoolean(String.valueOf(o));
    }

    private static int intOr(Object o, int def) {
        if (o == null) return def;
        if (o instanceof Number n) return n.intValue();
        try {
            return Integer.parseInt(String.valueOf(o));
        } catch (NumberFormatException e) {
            return def;
        }
    }

    private static Long lng(Object o) {
        if (o == null) return null;
        if (o instanceof Number n) return n.longValue();
        try {
            return Long.valueOf(String.valueOf(o));
        } catch (NumberFormatException e) {
            return null;
        }
    }
}
