package com.zioinfo.kintex.session;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.zioinfo.kintex.session.SessionDtos.MyRsvpDto;
import com.zioinfo.kintex.session.SessionDtos.RsvpResultDto;
import com.zioinfo.kintex.session.SessionDtos.SessionDto;
import com.zioinfo.kintex.visitor.notification.VisitorNotificationService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * F-B4 세션 정원 RSVP + F-C5 대기열·취소표 서비스.
 *
 * <p>신청: 정원 여유 → CONFIRMED(조건부 좌석 확보로 오버부킹 차단), 소진 → WAITLIST(순번 부여).
 * <p>취소: 확정 취소 시 좌석 반환 후 대기 1순위를 자동 승격(CONFIRMED)하고 인앱 알림 발행(취소표 알림).
 */
@Service
public class SessionService {

    private final SessionMapper mapper;
    private final VisitorNotificationService notifications;

    public SessionService(SessionMapper mapper, VisitorNotificationService notifications) {
        this.mapper = mapper;
        this.notifications = notifications;
    }

    /** 행사 세션 목록(userId null 이면 내 상태 없음 — 공개 조회). */
    @Transactional(readOnly = true)
    public List<SessionDto> list(String eventId, String userId) {
        if (eventId == null || eventId.isBlank()) {
            throw new ApiException(ErrorCode.VALIDATION, "행사 식별자가 필요합니다.");
        }
        // 익명(공개) 조회는 sentinel('')로 — null JDBC 바인딩 회피(PG 파라미터 타입 추론 안전).
        String uid = userId == null ? "" : userId;
        return mapper.listSessions(eventId, uid).stream().map(SessionService::toSession).toList();
    }

    /** 내 RSVP 목록. */
    @Transactional(readOnly = true)
    public List<MyRsvpDto> mine(String userId) {
        return mapper.myRsvps(userId).stream()
                .map(m -> new MyRsvpDto(str(m.get("sessionId")), str(m.get("title")), str(m.get("room")),
                        str(m.get("track")), str(m.get("startsAt")), str(m.get("status")),
                        intOrNull(m.get("waitlistPos"))))
                .toList();
    }

    /** 세션 신청 — 정원 여유 시 확정, 소진 시 대기 등록. */
    @Transactional
    public RsvpResultDto rsvp(String userId, String sessionId) {
        Map<String, Object> s = mapper.findSession(sessionId);
        if (s == null) {
            throw new ApiException(ErrorCode.NOT_FOUND, "세션을 찾을 수 없습니다.");
        }
        if (!"open".equals(str(s.get("status")))) {
            throw new ApiException(ErrorCode.CONFLICT, "신청이 마감된 세션입니다.");
        }
        Map<String, Object> existing = mapper.findRsvp(sessionId, userId);
        if (existing != null && !"CANCELLED".equals(str(existing.get("status")))) {
            throw new ApiException(ErrorCode.CONFLICT, "이미 신청한 세션입니다.");
        }
        String eventId = str(s.get("eventId"));

        boolean confirmed = mapper.takeSeat(sessionId) == 1;
        String status = confirmed ? "CONFIRMED" : "WAITLIST";
        Integer waitPos = confirmed ? null : mapper.maxWaitlistPos(sessionId) + 1;

        if (existing != null) {
            // 취소 후 재신청 — 기존 행 재사용.
            mapper.updateRsvp(str(existing.get("id")), status, waitPos);
        } else {
            Map<String, Object> row = new java.util.HashMap<>();
            row.put("id", "rsvp-" + UUID.randomUUID());
            row.put("sessionId", sessionId);
            row.put("eventId", eventId);
            row.put("userId", userId);
            row.put("status", status);
            row.put("waitlistPos", waitPos);
            mapper.insertRsvp(row);
        }

        String msg = confirmed ? "좌석이 확정되었습니다." : "정원이 마감되어 대기열에 등록되었습니다(취소 발생 시 알림).";
        return new RsvpResultDto(sessionId, status, waitPos, msg);
    }

    /** 세션 신청 취소 — 확정이었으면 좌석 반환 + 대기 1순위 자동 승격 + 알림. */
    @Transactional
    public RsvpResultDto cancel(String userId, String sessionId) {
        Map<String, Object> mine = mapper.findRsvp(sessionId, userId);
        if (mine == null || "CANCELLED".equals(str(mine.get("status")))) {
            throw new ApiException(ErrorCode.NOT_FOUND, "취소할 신청 내역이 없습니다.");
        }
        boolean wasConfirmed = "CONFIRMED".equals(str(mine.get("status")));
        mapper.updateRsvp(str(mine.get("id")), "CANCELLED", null);

        if (wasConfirmed) {
            // 좌석 반환 후 대기 1순위 승격 시도.
            mapper.releaseSeat(sessionId);
            Map<String, Object> next = mapper.findNextWaitlist(sessionId);
            if (next != null && mapper.takeSeat(sessionId) == 1) {
                mapper.updateRsvp(str(next.get("id")), "CONFIRMED", null);
                String promotedUser = str(next.get("userId"));
                Map<String, Object> sess = mapper.findSession(sessionId);
                String title = sess == null ? "세션" : str(sess.get("title"));
                notifications.publish(promotedUser, "WAITLIST_PROMOTED",
                        "대기하신 세션 좌석이 확정되었습니다",
                        "‘" + title + "’ 세션에 취소표가 발생하여 좌석이 확정되었습니다.",
                        "SESSION", sessionId);
            }
        }
        return new RsvpResultDto(sessionId, "CANCELLED", null, "신청이 취소되었습니다.");
    }

    // ── helpers ──
    private static SessionDto toSession(Map<String, Object> m) {
        return new SessionDto(
                str(m.get("id")), str(m.get("title")), str(m.get("speaker")), str(m.get("room")),
                str(m.get("track")), str(m.get("startsAt")), str(m.get("endsAt")),
                toInt(m.get("capacity"), 0), toInt(m.get("seatTaken"), 0),
                intOrNull(m.get("remaining")), bool(m.get("full")), str(m.get("status")),
                str(m.get("myStatus")));
    }

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

    private static boolean bool(Object o) {
        return o instanceof Boolean b && b;
    }

    private static int toInt(Object o, int def) {
        return o instanceof Number n ? n.intValue() : def;
    }

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