package com.zioinfo.kintex.publicsite.subscription;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.zioinfo.kintex.publicsite.subscription.dto.SubscriptionDtos.SubscribeRequest;
import com.zioinfo.kintex.publicsite.subscription.dto.SubscriptionDtos.SubscribeResult;
import com.zioinfo.kintex.publicsite.subscription.dto.SubscriptionDtos.UnsubscribeResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;

/**
 * 관심 행사 구독(F-C1) 서비스 — 공개(비인증) 구독/해지. 티켓 오픈 알림 발송은 {@link TicketOpenNotifier}(스케줄러).
 * <p>PII 최소화(§0-3): 원문 email 은 저장·발송 경로 한정이며 응답에는 email_masked 만 담는다.
 * 구독 응답에도 원문·전체 목록은 노출하지 않는다(공개 엔드포인트).
 */
@Service
public class EventSubscriptionService {

    private static final int RATE_WINDOW_SECONDS = 60;   // 동일 이메일 1분 1건
    private static final Pattern EMAIL = Pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");

    private final EventSubscriptionMapper mapper;

    public EventSubscriptionService(EventSubscriptionMapper mapper) {
        this.mapper = mapper;
    }

    /**
     * 관심 행사 구독(공개). 개인정보 동의 필수 · 이메일 형식 검증 · 종료 행사 거부 · 동일 이메일 1분 rate 제어.
     * 멱등: 기존 활성 구독이면 alreadySubscribed=true. 해지되었던 건은 재활성화한다.
     */
    @Transactional
    public SubscribeResult subscribe(String eventId, SubscribeRequest req) {
        if (req == null || !Boolean.TRUE.equals(req.agreePrivacy())) {
            throw new ApiException(ErrorCode.VALIDATION, "개인정보 수집·이용 동의(필수)가 필요합니다.");
        }
        String email = nullIfBlank(req.email());
        if (email == null || !EMAIL.matcher(email).matches()) {
            throw new ApiException(ErrorCode.VALIDATION, "이메일 형식이 올바르지 않습니다.");
        }
        if (mapper.eventExists(eventId) == 0) {
            throw new ApiException(ErrorCode.NOT_FOUND, "존재하지 않는 행사입니다.");
        }
        Integer ended = mapper.isEnded(eventId);
        if (ended != null && ended == 1) {
            throw new ApiException(ErrorCode.VALIDATION, "이미 종료된 행사는 구독할 수 없습니다.");
        }

        String masked = maskEmail(email);

        // 기존 구독 확인(멱등) — 활성이면 그대로, 해지 상태면 재활성화.
        Map<String, Object> existing = mapper.findByEventEmail(eventId, email);
        if (existing != null) {
            String status = str(existing.get("status"));
            if ("active".equals(status)) {
                return new SubscribeResult(eventId, null, masked, "active", true);
            }
            mapper.reactivate(str(existing.get("id")));
            return new SubscribeResult(eventId, null, masked, "active", false);
        }

        // 신규 — rate 제어(동일 이메일 1분 1건)
        if (mapper.countRecentByEmail(email, RATE_WINDOW_SECONDS) > 0) {
            throw new ApiException(ErrorCode.CONFLICT,
                    "같은 이메일로 잠시 전 구독 요청이 있었습니다. 1분 후 다시 시도해 주세요.");
        }
        String id = "es-" + UUID.randomUUID().toString().replace("-", "").substring(0, 18);
        String token = "estok-" + UUID.randomUUID().toString().replace("-", "");
        mapper.insert(id, eventId, email, masked, token);
        return new SubscribeResult(eventId, null, masked, "active", false);
    }

    /**
     * 원클릭 수신거부(공개·비인증). 불투명 토큰으로 멱등 해지. 토큰이 존재하면(이미 처리 포함) 성공 안내.
     */
    @Transactional
    public UnsubscribeResult unsubscribe(String token) {
        String t = nullIfBlank(token);
        if (t == null) {
            return new UnsubscribeResult(false, "유효하지 않은 요청입니다.");
        }
        int updated = mapper.unsubscribeByToken(t);
        if (updated > 0) {
            return new UnsubscribeResult(true, "구독이 해지되었습니다.");
        }
        // 이미 해지되었거나(멱등) 토큰 존재 여부로 성공/실패 구분.
        if (mapper.tokenExists(t) > 0) {
            return new UnsubscribeResult(true, "이미 수신거부 처리된 구독입니다.");
        }
        return new UnsubscribeResult(false, "유효하지 않은 수신거부 토큰입니다.");
    }

    // ── helpers ──
    /** 이메일 마스킹 — wat***@domain.com. 로그·응답 노출 안전 형식. */
    static String maskEmail(String email) {
        if (email == null || email.isBlank()) {
            return "***";
        }
        int at = email.indexOf('@');
        if (at <= 0) {
            return "***";
        }
        String local = email.substring(0, at);
        String domain = email.substring(at);
        if (local.length() <= 3) {
            return local.charAt(0) + "***" + domain;
        }
        return local.substring(0, 3) + "***" + domain;
    }

    private static String nullIfBlank(String s) {
        return (s == null || s.isBlank()) ? null : s.trim();
    }

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