package com.zioinfo.kintex.catalog;

import com.zioinfo.kintex.catalog.dto.CalendarDayDto;
import com.zioinfo.kintex.catalog.dto.ExhibitionDto;
import com.zioinfo.kintex.catalog.dto.HallUtilizationDto;
import com.zioinfo.kintex.tenant.TenantContext;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** 전시 일정 카탈로그 서비스 구현 — {@link EventCatalogMapper} 조회 결과를 DTO로 매핑·집계. */
@Service
public class EventCatalogServiceImpl implements EventCatalogService {

    /** 홀 가동률 그룹(프론트 sampleSchedule.HALL_GROUPS 와 정합). */
    private static final HallGroup[] HALL_GROUPS = {
            new HallGroup("홀 1-3", 1, 3),
            new HallGroup("홀 4-5", 4, 5),
            new HallGroup("홀 6-8", 6, 8),
            new HallGroup("홀 9-10", 9, 10),
    };

    /** "제N전시장" 접두(홀 번호 오인식 방지) 제거용. */
    private static final Pattern CENTER_PREFIX = Pattern.compile("제\\s*\\d+\\s*전시장");
    private static final Pattern DIGITS = Pattern.compile("\\d+");

    private final EventCatalogMapper mapper;

    public EventCatalogServiceImpl(EventCatalogMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public List<ExhibitionDto> list(String year, String status, String category, int limit, int offset) {
        List<Map<String, Object>> rows =
                mapper.findEvents(TenantContext.currentTenantId(),
                        nullIfBlank(year), nullIfBlank(status), nullIfBlank(category), limit, offset);
        List<ExhibitionDto> out = new ArrayList<>(rows == null ? 0 : rows.size());
        if (rows != null) {
            for (Map<String, Object> r : rows) {
                out.add(new ExhibitionDto(
                        str(r.get("id")), str(r.get("name")),
                        str(r.get("startDate")), str(r.get("endDate")), str(r.get("status")),
                        str(r.get("hallLabel")), str(r.get("category")), intOrNull(r.get("estVisitors"))));
            }
        }
        return out;
    }

    @Override
    public List<HallUtilizationDto> hallUtilization(int year, int month) {
        MonthRange mr = MonthRange.of(year, month);
        List<Map<String, Object>> rows =
                mapper.findEventsOverlappingMonth(TenantContext.currentTenantId(), mr.startYmd(), mr.endYmd());

        // 홀 그룹별 점유 일자 집합
        List<boolean[]> busy = new ArrayList<>();
        for (int g = 0; g < HALL_GROUPS.length; g++) {
            busy.add(new boolean[mr.days() + 1]); // 1-based day index
        }
        if (rows != null) {
            for (Map<String, Object> r : rows) {
                int[] halls = hallNumbers(str(r.get("hallLabel")));
                if (halls.length == 0) {
                    continue;
                }
                int from = dayInMonth(str(r.get("startDate")), mr, true);
                int to = dayInMonth(str(r.get("endDate")), mr, false);
                for (int g = 0; g < HALL_GROUPS.length; g++) {
                    HallGroup hg = HALL_GROUPS[g];
                    boolean inGroup = false;
                    for (int n : halls) {
                        if (n >= hg.lo && n <= hg.hi) {
                            inGroup = true;
                            break;
                        }
                    }
                    if (!inGroup) {
                        continue;
                    }
                    boolean[] flags = busy.get(g);
                    for (int d = from; d <= to; d++) {
                        flags[d] = true;
                    }
                }
            }
        }

        List<HallUtilizationDto> out = new ArrayList<>(HALL_GROUPS.length);
        for (int g = 0; g < HALL_GROUPS.length; g++) {
            boolean[] flags = busy.get(g);
            int busyDays = 0;
            for (int d = 1; d <= mr.days(); d++) {
                if (flags[d]) {
                    busyDays++;
                }
            }
            int pct = mr.days() == 0 ? 0 : Math.round(busyDays * 100f / mr.days());
            out.add(new HallUtilizationDto(HALL_GROUPS[g].label, pct, busyDays, mr.days()));
        }
        return out;
    }

    @Override
    public List<CalendarDayDto> calendar(int year, int month) {
        MonthRange mr = MonthRange.of(year, month);
        List<Map<String, Object>> rows =
                mapper.findEventsOverlappingMonth(TenantContext.currentTenantId(), mr.startYmd(), mr.endYmd());
        int[] counts = new int[mr.days() + 1]; // 1-based
        if (rows != null) {
            for (Map<String, Object> r : rows) {
                int from = dayInMonth(str(r.get("startDate")), mr, true);
                int to = dayInMonth(str(r.get("endDate")), mr, false);
                for (int d = from; d <= to; d++) {
                    counts[d]++;
                }
            }
        }
        List<CalendarDayDto> out = new ArrayList<>();
        for (int d = 1; d <= mr.days(); d++) {
            if (counts[d] > 0) {
                out.add(new CalendarDayDto(mr.ymd(d), counts[d]));
            }
        }
        return out;
    }

    /** 홀 라벨 문자열에서 홀 번호(1~10)만 추출. "제N전시장" 접두는 제거해 오인식 방지. */
    static int[] hallNumbers(String hallLabel) {
        if (hallLabel == null || hallLabel.isBlank()) {
            return new int[0];
        }
        String cleaned = CENTER_PREFIX.matcher(hallLabel).replaceAll(" ");
        Matcher m = DIGITS.matcher(cleaned);
        List<Integer> nums = new ArrayList<>();
        while (m.find()) {
            try {
                int n = Integer.parseInt(m.group());
                if (n >= 1 && n <= 10) {
                    nums.add(n);
                }
            } catch (NumberFormatException ignore) {
                // skip
            }
        }
        int[] arr = new int[nums.size()];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = nums.get(i);
        }
        return arr;
    }

    /** 행사 시작/종료 YMD 를 월 범위로 클램프한 일(day) 인덱스. start=true 면 하한 1, end 면 상한 days. */
    private static int dayInMonth(String ymd, MonthRange mr, boolean isStart) {
        if (ymd == null) {
            return isStart ? 1 : mr.days();
        }
        // ymd 가 이 달에 속하면 그 일, 이전 달 시작이면 1, 다음 달 종료면 마지막 일
        String prefix = mr.prefix();
        if (ymd.startsWith(prefix)) {
            try {
                return Integer.parseInt(ymd.substring(8, 10));
            } catch (RuntimeException e) {
                return isStart ? 1 : mr.days();
            }
        }
        return isStart ? 1 : mr.days();
    }

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

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

    private static Integer intOrNull(Object o) {
        if (o == null) return null;
        if (o instanceof Number n) return n.intValue();
        try {
            return Integer.valueOf(String.valueOf(o));
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private record HallGroup(String label, int lo, int hi) {
    }

    /** 월 범위(정규화된 1~12 월) — 시작/종료 YMD·일수·YMD 헬퍼. */
    private record MonthRange(int year, int month, int days) {
        static MonthRange of(int year, int monthRaw) {
            int m = Math.min(Math.max(monthRaw, 1), 12);
            int y = year <= 0 ? LocalDate.now().getYear() : year;
            int d = LocalDate.of(y, m, 1).lengthOfMonth();
            return new MonthRange(y, m, d);
        }

        String prefix() {
            return String.format("%04d-%02d", year, month);
        }

        String startYmd() {
            return prefix() + "-01";
        }

        String endYmd() {
            return String.format("%04d-%02d-%02d", year, month, days);
        }

        String ymd(int day) {
            return String.format("%04d-%02d-%02d", year, month, day);
        }
    }
}
