package com.zioinfo.kintex.publicsite;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.zioinfo.kintex.publicsite.dto.InquiryReceiptDto;
import com.zioinfo.kintex.publicsite.dto.InquiryRequest;
import com.zioinfo.kintex.publicsite.dto.PublicEventDto;
import com.zioinfo.kintex.publicsite.dto.PublicFloorplanDto;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;

/**
 * 공개 홍보 사이트(M12) 서비스 — 카탈로그 read-only 조회 + 문의 접수(rate 제어).
 * 전 경로 비인증 공개. 공개 응답에는 내부 식별자·PII·원가를 담지 않는다.
 */
@Service
public class PublicSiteService {

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

    private final PublicSiteMapper mapper;

    public PublicSiteService(PublicSiteMapper mapper) {
        this.mapper = mapper;
    }

    // ── 카탈로그 ────────────────────────────────────────────────────────────
    public List<PublicEventDto> listEvents(String year, String q, int limit, int offset) {
        return mapper.findEvents(nullIfBlank(year), nullIfBlank(q), limit, offset)
                .stream().map(this::toEvent).toList();
    }

    public PublicEventDto getEvent(String eventId) {
        Map<String, Object> row = mapper.findEvent(eventId);
        if (row == null) throw new ApiException(ErrorCode.NOT_FOUND);
        return toEvent(row);
    }

    public PublicFloorplanDto getFloorplan(String eventId) {
        Map<String, Object> ev = mapper.findEvent(eventId);
        if (ev == null) throw new ApiException(ErrorCode.NOT_FOUND);
        List<PublicFloorplanDto.Hall> halls = mapper.findEventHalls(eventId).stream()
                .map(m -> new PublicFloorplanDto.Hall(
                        str(m.get("hallId")), str(m.get("label")),
                        toInt(m.get("center")), Boolean.TRUE.equals(m.get("primary"))))
                .toList();
        List<PublicFloorplanDto.Booth> booths = mapper.findPublicBooths(eventId, BOOTH_LIMIT).stream()
                .map(m -> new PublicFloorplanDto.Booth(
                        str(m.get("boothNo")), str(m.get("companyName")), str(m.get("boothType")),
                        str(m.get("status")), toDouble(m.get("cx")), toDouble(m.get("cy")),
                        str(m.get("hallLabel"))))
                .toList();
        return new PublicFloorplanDto(str(ev.get("id")), str(ev.get("name")), halls, booths);
    }

    // ── 문의 접수 ────────────────────────────────────────────────────────────
    @Transactional
    public InquiryReceiptDto submitInquiry(InquiryRequest req) {
        String name = nullIfBlank(req.contactName());
        String email = nullIfBlank(req.email());
        String phone = nullIfBlank(req.phone());

        if (!Boolean.TRUE.equals(req.agreePrivacy())) {
            throw new ApiException(ErrorCode.VALIDATION, "개인정보 수집·이용 동의(필수)가 필요합니다.");
        }
        if (name == null) {
            throw new ApiException(ErrorCode.VALIDATION, "담당자 이름을 입력해 주세요.");
        }
        if (email == null && phone == null) {
            throw new ApiException(ErrorCode.VALIDATION, "연락 가능한 이메일 또는 전화번호를 입력해 주세요.");
        }
        if (email != null && !EMAIL.matcher(email).matches()) {
            throw new ApiException(ErrorCode.VALIDATION, "이메일 형식이 올바르지 않습니다.");
        }
        if (nullIfBlank(req.message()) == null) {
            throw new ApiException(ErrorCode.VALIDATION, "문의 내용을 입력해 주세요.");
        }

        // 단순 rate: 동일 이메일 1분 1건(이메일 있을 때만 적용)
        if (email != null && mapper.countRecentByEmail(email, RATE_WINDOW_SECONDS) > 0) {
            throw new ApiException(ErrorCode.CONFLICT,
                    "같은 이메일로 잠시 전 문의가 접수되었습니다. 1분 후 다시 시도해 주세요.");
        }

        String receiptNo = generateReceiptNo();
        mapper.insertInquiry(
                UUID.randomUUID().toString(), receiptNo,
                nullIfBlank(req.inquiryType()), nullIfBlank(req.company()), name, email, phone,
                nullIfBlank(req.scale()), nullIfBlank(req.hallPref()), nullIfBlank(req.message()),
                true, Boolean.TRUE.equals(req.agreeMarketing()));
        return new InquiryReceiptDto(receiptNo, "received");
    }

    // ── helpers ──────────────────────────────────────────────────────────────
    private PublicEventDto toEvent(Map<String, Object> m) {
        return new PublicEventDto(
                str(m.get("id")), str(m.get("name")), str(m.get("startDate")), str(m.get("endDate")),
                str(m.get("status")), str(m.get("hallLabel")), toInt(m.get("boothCount")),
                str(m.get("posterUrl")));
    }

    private String generateReceiptNo() {
        String day = LocalDate.now().format(RECEIPT_DAY);
        int rand = ThreadLocalRandom.current().nextInt(0, 10000);
        return "KTX-INQ-" + day + "-" + String.format("%04d", rand);
    }

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

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

    private static Integer toInt(Object o) {
        return (o instanceof Number n) ? n.intValue() : null;
    }

    private static Double toDouble(Object o) {
        return (o instanceof Number n) ? n.doubleValue() : null;
    }
}
