package com.zioinfo.kintex.publicsite.subscription;

import com.zioinfo.kintex.mail.MailProperties;
import com.zioinfo.kintex.mail.MailService;
import com.zioinfo.kintex.mail.MailTemplates;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 티켓 오픈 감지 스케줄러(F-C1) — 관심 행사 구독자에게 티켓 판매 개시 알림을 1회 발송한다.
 *
 * <p><b>동작</b>: 60초 폴러가 {@link EventSubscriptionMapper#findPendingTicketOpen}(오픈된 행사의 미통지 활성 구독)을
 * 조회하고, 각 건을 <b>조건부 markNotified</b>(이중 발송 방지)로 선점한 뒤 자체 SMTP({@link MailService})로 발송한다.
 *
 * <p><b>데모 안전장치(EDM 재사용)</b>: {@link MailProperties#isEdmTestMode()}(기본 true)면 <b>dry-run 모드</b> —
 * 실 구독자에게 보내지 않고 화이트리스트({@code EDM_TEST_RECIPIENTS})로만 발송(비면 카운트만)한다. 실 대량 발송은
 * env 로 {@code kintex.mail.edm-test-mode=false} + {@code MAIL_ENABLED=true} 설정 시에만 열린다. 모드와 무관하게
 * 처리 건은 notified 로 마킹하여 스케줄러 재처리(로그 폭주)를 막는다.
 *
 * <p><b>PII(§0-3)</b>: 원문 이메일은 발송 경로 한정 — 로그·응답에는 건수만 남긴다({@link MailService}가 마스킹 이력).
 * <p>{@code @Scheduled} 전역 활성화는 기존 {@code WebhookSchedulingConfig(@EnableScheduling)} 가 담당한다.
 */
@Component
public class TicketOpenNotifier {

    private static final Logger log = LoggerFactory.getLogger(TicketOpenNotifier.class);

    /** 1회 스윕 처리 상한(폭주 방지). */
    private static final int SWEEP_LIMIT = 300;

    private final EventSubscriptionMapper mapper;
    private final MailService mailService;
    private final MailProperties mailProperties;

    public TicketOpenNotifier(EventSubscriptionMapper mapper,
                              MailService mailService,
                              MailProperties mailProperties) {
        this.mapper = mapper;
        this.mailService = mailService;
        this.mailProperties = mailProperties;
    }

    /** 오픈 감지 폴러 — 기동 45초 후 최초 실행, 이후 60초 고정 지연(이전 스윕 종료 기준). */
    @Scheduled(initialDelay = 45_000, fixedDelay = 60_000)
    public void sweep() {
        try {
            runOnce();
        } catch (Exception e) {
            // 스택트레이스·민감정보 미노출. 다음 주기에 재시도.
            log.warn("F-C1 티켓 오픈 알림 스윕 실패: {}", e.getClass().getSimpleName());
        }
    }

    /**
     * 오픈된 행사의 미통지 구독을 발송 처리한다.
     * @return 이번 스윕에서 통지 처리(마킹)한 구독 건수.
     */
    public int runOnce() {
        List<Map<String, Object>> pending;
        try {
            pending = mapper.findPendingTicketOpen(SWEEP_LIMIT);
        } catch (RuntimeException e) {
            log.warn("ticket-open sweep skipped: {}", e.getClass().getSimpleName());
            return 0;
        }
        if (pending == null || pending.isEmpty()) {
            return 0;
        }

        boolean testMode = mailProperties.isEdmTestMode();
        List<String> whitelist = sanitize(mailProperties.getEdmTestRecipients());
        String baseUrl = trimTrailingSlash(mailProperties.getWebBaseUrl());

        int processed = 0;
        for (Map<String, Object> row : pending) {
            String id = str(row.get("id"));
            if (id == null) {
                continue;
            }
            // 조건부 선점 — 이미 통지된 건(경쟁)은 0 → 스킵(이중 발송 방지).
            if (mapper.markNotified(id) == 0) {
                continue;
            }
            processed++;

            String eventName = str(row.get("eventName"));
            String token = str(row.get("unsubToken"));
            String bookUrl = baseUrl + "/public/events/" + urlEnc(str(row.get("eventId"))) + "/tickets";
            String unsubUrl = baseUrl + "/api/public/subscriptions/unsubscribe?token=" + urlEnc(token);
            String subject = "[KINTEX] 관심 행사 티켓 예매 시작 — " + (eventName == null ? "행사" : eventName);
            String html = MailTemplates.ticketOpen(eventName, bookUrl, unsubUrl);

            if (testMode) {
                // dry-run — 실 구독자 미발송. 화이트리스트로만 발송(비면 카운트만).
                for (String to : whitelist) {
                    mailService.send("TICKET_OPEN_TEST", id, to, "[TEST] " + subject, html);
                }
            } else {
                // 운영 실발송 — 구독자 원문 이메일(발송 경로 한정, 로그 미노출).
                mailService.send("TICKET_OPEN", id, str(row.get("email")), subject, html);
            }
        }
        if (processed > 0) {
            log.info("F-C1 티켓 오픈 알림 처리 {}건{}", processed, testMode ? " (테스트/dry-run)" : "");
        }
        return processed;
    }

    // ── helpers ──
    private static List<String> sanitize(List<String> in) {
        List<String> out = new ArrayList<>();
        if (in != null) {
            for (String s : in) {
                if (s != null && !s.isBlank()) {
                    out.add(s.trim());
                }
            }
        }
        return out;
    }

    private static String trimTrailingSlash(String s) {
        if (s == null || s.isBlank()) {
            return "";
        }
        String t = s.trim();
        return t.endsWith("/") ? t.substring(0, t.length() - 1) : t;
    }

    private static String urlEnc(String s) {
        return URLEncoder.encode(s == null ? "" : s, StandardCharsets.UTF_8);
    }

    private static String str(Object o) {
        return o == null ? null : String.valueOf(o);
    }
}
