package com.zioinfo.kintex.system.holiday;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import org.springframework.stereotype.Service;

import java.time.Year;
import java.util.List;
import java.util.Map;

/**
 * 공휴일 서비스 — V41 holiday(2024~2050) 조회·임시공휴일 관리.
 * <ul>
 *   <li>관리자 목록: year 또는 from/to 로 조회(둘 다 없으면 올해 전체).</li>
 *   <li>공개(인증) 조회: 캘린더 붉은색 표시용 — 실제 휴무만.</li>
 *   <li>추가: PK(tenant_id, ymd) 사전 존재확인 → 충돌 시 409.</li>
 * </ul>
 * 테넌트는 {@code KintexPrincipal.tenantId()}로 스코프(멀티테넌시). V41 시드/스키마는 불변.
 */
@Service
public class HolidayService {

    /** 임시 조회 범위 상한(2050까지 시드 존재). 방어적 클램프. */
    private static final int MIN_YEAR = 2000;
    private static final int MAX_YEAR = 2100;

    private final HolidayMapper mapper;

    public HolidayService(HolidayMapper mapper) {
        this.mapper = mapper;
    }

    /** 관리자 목록 — year 우선, 없으면 from/to, 둘 다 없으면 올해. 휴무 off 포함. */
    public List<HolidayDto> adminList(String tenantId, Integer year, String from, String to) {
        String[] range = resolveRange(year, from, to);
        return mapper.findRange(tenantId, range[0], range[1]).stream().map(HolidayService::toDto).toList();
    }

    /** 공개(인증) 조회 — 기간 내 실제 휴무만(캘린더 붉은색). from/to 없으면 올해. */
    public List<HolidayDto> publicRange(String tenantId, String from, String to) {
        String[] range = resolveRange(null, from, to);
        return mapper.findActiveRange(tenantId, range[0], range[1]).stream().map(HolidayService::toDto).toList();
    }

    /** 데이터가 존재하는 연도 목록(년 선택). */
    public List<Integer> years(String tenantId) {
        return mapper.findYears(tenantId);
    }

    /** 임시공휴일 추가 — 중복 시 409. */
    public HolidayDto create(String tenantId, HolidayCreateRequest req) {
        String ymd = req.ymd();
        if (mapper.exists(tenantId, ymd) > 0) {
            throw new ApiException(ErrorCode.CONFLICT, "이미 등록된 날짜입니다: " + ymd);
        }
        mapper.insert(tenantId, ymd, req.name().trim(),
                req.isSubstitute() != null && req.isSubstitute());
        return new HolidayDto(ymd, req.name().trim(),
                req.isSubstitute() != null && req.isSubstitute(), true);
    }

    /** 삭제 — 없으면 404. */
    public void delete(String tenantId, String ymd) {
        validateYmd(ymd);
        if (mapper.delete(tenantId, ymd) == 0) {
            throw new ApiException(ErrorCode.NOT_FOUND, "공휴일을 찾을 수 없습니다: " + ymd);
        }
    }

    /** 휴무 여부 토글(임시휴무 on/off) — 없으면 404. */
    public void toggle(String tenantId, String ymd, boolean isHoliday) {
        validateYmd(ymd);
        if (mapper.updateHoliday(tenantId, ymd, isHoliday) == 0) {
            throw new ApiException(ErrorCode.NOT_FOUND, "공휴일을 찾을 수 없습니다: " + ymd);
        }
    }

    // ── 내부 헬퍼 ──

    /** year 또는 from/to → [fromYmd, toYmd]. year 우선, from/to 부분지정 허용, 둘 다 없으면 올해. */
    private static String[] resolveRange(Integer year, String from, String to) {
        if (year != null) {
            int y = clampYear(year);
            return new String[] {y + "-01-01", y + "-12-31"};
        }
        if (from != null && to != null) {
            validateYmd(from);
            validateYmd(to);
            return new String[] {from, to};
        }
        int y = Year.now().getValue();
        String start = from != null ? valid(from) : y + "-01-01";
        String end = to != null ? valid(to) : y + "-12-31";
        return new String[] {start, end};
    }

    private static int clampYear(int y) {
        return Math.max(MIN_YEAR, Math.min(MAX_YEAR, y));
    }

    private static String valid(String ymd) {
        validateYmd(ymd);
        return ymd;
    }

    private static void validateYmd(String ymd) {
        if (ymd == null || !ymd.matches("\\d{4}-\\d{2}-\\d{2}")) {
            throw new ApiException(ErrorCode.VALIDATION, "날짜는 yyyy-MM-dd 형식이어야 합니다.");
        }
    }

    private static HolidayDto toDto(Map<String, Object> r) {
        return new HolidayDto(
                str(r.get("ymd")),
                str(r.get("name")),
                Boolean.TRUE.equals(r.get("isSubstitute")),
                Boolean.TRUE.equals(r.get("isHoliday")));
    }

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