package com.zioinfo.kintex.congestion;

import com.zioinfo.kintex.congestion.dto.CongestionDtos.CongestionAreaDto;
import com.zioinfo.kintex.congestion.dto.CongestionDtos.CongestionOverviewDto;
import com.zioinfo.kintex.ops.OpsService;
import com.zioinfo.kintex.ops.dto.OpsDto;
import com.zioinfo.kintex.parking.ParkingService;
import com.zioinfo.kintex.parking.dto.ParkingDtos.ParkingLotStatusDto;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * 관람객 혼잡 안내 서비스(F-C3) — M14 현장운영(OpsService, 체크인 파생 혼잡) 실집계를 재사용해
 * 입장 게이트(홀 입장)·주차·인기 공간(세션) 혼잡을 관람객 3단계(여유/보통/혼잡)로 재구성한다.
 *
 * <p>재사용 원칙: 혼잡 산출 로직을 중복 구현하지 않고 {@link OpsService}(홀 혼잡)와
 * {@link ParkingService}(주차 점유)의 결과를 관람객 관점으로 매핑한다. 신규 계측·하드웨어 없음.
 */
@Service
public class CongestionService {

    /** 인기 공간(세션) 노출 상한 — 현장 인원 상위 N개 홀. */
    private static final int POPULAR_LIMIT = 3;
    private static final DateTimeFormatter TS = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

    private final OpsService opsService;
    private final ParkingService parkingService;

    public CongestionService(OpsService opsService, ParkingService parkingService) {
        this.opsService = opsService;
        this.parkingService = parkingService;
    }

    public CongestionOverviewDto getPublicCongestion(String eventId) {
        OpsDto ops = opsService.getOps(eventId);

        // 입장 게이트 = 홀 입장 혼잡(OpsService heat 재사용). 게이트 점유율은 계측 부재 → null.
        List<CongestionAreaDto> gates = new ArrayList<>();
        for (OpsDto.Hall h : ops.halls()) {
            String level = mapHeat(h.heat());
            gates.add(new CongestionAreaDto(h.hallId(), h.label(), level, label(level), null));
        }

        // 인기 공간(세션) = 현장 인원 상위 홀(혼잡 홀). 세션 단위 계측 부재 → 홀 파생 근사.
        List<CongestionAreaDto> popular = ops.halls().stream()
                .filter(h -> h.estPeople() > 0)
                .sorted(Comparator.comparingLong(OpsDto.Hall::estPeople).reversed())
                .limit(POPULAR_LIMIT)
                .map(h -> {
                    String level = mapHeat(h.heat());
                    return new CongestionAreaDto(h.hallId(), h.label(), level, label(level), null);
                })
                .toList();

        // 주차 = ParkingService 점유율(시뮬레이션 어댑터). 점유율 그대로 노출.
        List<CongestionAreaDto> parking = new ArrayList<>();
        for (ParkingLotStatusDto lot : parkingService.getLotStatus(eventId)) {
            parking.add(new CongestionAreaDto(lot.lotId(), lot.name(),
                    lot.congestionLevel(), lot.congestionLabel(), lot.occupancyPercent()));
        }

        // 전체 = 게이트 + 주차 중 가장 혼잡한 단계(관람객 안전 안내는 보수적으로).
        String overall = worstLevel(gates, parking);
        return new CongestionOverviewDto(eventId, overall, label(overall), ops.onSiteCount(),
                gates, parking, popular, LocalDateTime.now().format(TS));
    }

    /** OpsService heat(smooth/moderate/busy) → 관람객 3단계 코드. */
    private static String mapHeat(String heat) {
        if (heat == null) {
            return "FREE";
        }
        return switch (heat) {
            case "busy" -> "BUSY";
            case "moderate" -> "NORMAL";
            default -> "FREE";
        };
    }

    private static String label(String level) {
        return switch (level) {
            case "BUSY" -> "혼잡";
            case "NORMAL" -> "보통";
            default -> "여유";
        };
    }

    /** 여러 영역 중 가장 혼잡한 단계(FREE<NORMAL<BUSY). 비면 FREE. */
    @SafeVarargs
    private static String worstLevel(List<CongestionAreaDto>... groups) {
        int worst = 0; // 0=FREE,1=NORMAL,2=BUSY
        for (List<CongestionAreaDto> g : groups) {
            for (CongestionAreaDto a : g) {
                worst = Math.max(worst, rank(a.level()));
            }
        }
        return switch (worst) {
            case 2 -> "BUSY";
            case 1 -> "NORMAL";
            default -> "FREE";
        };
    }

    private static int rank(String level) {
        return switch (level == null ? "FREE" : level) {
            case "BUSY" -> 2;
            case "NORMAL" -> 1;
            default -> 0;
        };
    }
}
