package com.zioinfo.kintex.home;

import com.zioinfo.kintex.auth.KintexPrincipal;
import com.zioinfo.kintex.home.dto.HomeDtos.HallCenter;
import com.zioinfo.kintex.home.dto.HomeDtos.ShowcaseItem;
import com.zioinfo.kintex.home.dto.HomeDtos.SummaryResponse;
import com.zioinfo.kintex.home.dto.HomeDtos.SummaryTile;
import com.zioinfo.kintex.home.dto.HomeDtos.TodoItem;
import com.zioinfo.kintex.home.dto.HomeDtos.TodoResponse;
import com.zioinfo.kintex.tenant.TenantContext;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * SCR-HOME 집계 서비스 — 읽기 전용. 역할(JWT roleCode·hallManager·행사 역할) 기반으로 원천을 병합하고
 * 기한 임박순으로 정렬한다. 병합·정렬·심각도·라벨 조립 등 파생 로직은 이 서비스 한 곳에 둔다.
 */
@Service
public class HomeService {

    /** 서류 마감 임박 지평(D-7 이내). 프론트 계약(SCR-HOME 내 할 일 D-n)과 정합. */
    private static final int DOC_HORIZON_DAYS = 7;
    /** 내 할 일 표시 상한(design.md: 최대 6행 + 전체 보기). */
    private static final int TODO_LIMIT = 6;

    private final HomeMapper mapper;

    public HomeService(HomeMapper mapper) {
        this.mapper = mapper;
    }

    // ── 내 할 일 ──────────────────────────────────────────────────────────────────

    public TodoResponse getTodos(KintexPrincipal principal) {
        String userId = principal.userId();
        String tenantId = TenantContext.currentTenantId();
        boolean broad = isBroadOversight(principal);
        String horizon = LocalDate.now().plusDays(DOC_HORIZON_DAYS).toString();

        // 원천별 자기선별(각 서브쿼리가 event_member 멤버십·role_code 로 스스로 역할 스코프됨 → 역할별 필터는
        // 여기서 하드 게이트하지 않고 원천 스코프에 위임한다: CONTRACTOR 는 정산/서류 미노출, EXHIBITOR 는 옥션낙찰
        // 미노출 등. 다중 역할 사용자도 해당 행사 역할에 맞는 항목만 병합된다). broad(홀매니저·관리자)는 전 테넌트.
        List<Map<String, Object>> rows = new ArrayList<>();
        rows.addAll(mapper.findApprovalTodos(userId));
        rows.addAll(mapper.findDocumentTodos(userId, tenantId, horizon, broad));
        rows.addAll(mapper.findMilestoneTodos(userId, tenantId, horizon, broad));
        rows.addAll(mapper.findAuctionAwardTodos(userId, tenantId, broad));
        rows.addAll(mapper.findAuctionBidTodos(userId, tenantId));
        rows.addAll(mapper.findSettlementTodos(userId, tenantId, broad));
        rows.addAll(mapper.findCheckinPrepTodos(userId, tenantId, broad));
        rows.addAll(mapper.findLeadFollowupTodos(userId, tenantId, broad));

        List<TodoItem> items = new ArrayList<>();
        for (Map<String, Object> r : rows) {
            String type = str(r.get("type"));
            Integer dday = toInt(r.get("dday"));
            items.add(new TodoItem(
                    type,
                    str(r.get("title")),
                    str(r.get("target")),
                    str(r.get("dueDate")),
                    dday,
                    severityFor(type, dday),
                    str(r.get("route"))));
        }

        // 마감 임박 우선순위(2단): ① 심각도 랭크(경과 → 임박 → 일반) ② 잔여일 오름차순(기한 없는 건은 그룹 내 후순위).
        // 이로써 경과·D-3 이내 항목이 항상 상단으로 부각되고(프론트가 강조색 렌더), 핫리드·체크인 준비도 임박 그룹에서 노출된다.
        items.sort(Comparator
                .comparingInt((TodoItem t) -> severityRank(t.severity()))
                .thenComparingInt(t -> t.dday() == null ? Integer.MAX_VALUE : t.dday()));

        int total = items.size();
        List<TodoItem> top = items.size() > TODO_LIMIT ? items.subList(0, TODO_LIMIT) : items;
        return new TodoResponse(total, new ArrayList<>(top));
    }

    // ── 쇼케이스 ──────────────────────────────────────────────────────────────────

    public List<ShowcaseItem> getShowcase(String tabParam) {
        String tab = normalizeTab(tabParam);
        String tenantId = TenantContext.currentTenantId();
        List<Map<String, Object>> rows = mapper.findShowcase(tenantId, tab);
        List<ShowcaseItem> out = new ArrayList<>();
        for (Map<String, Object> r : rows) {
            out.add(new ShowcaseItem(
                    str(r.get("id")),
                    str(r.get("name")),
                    str(r.get("startDate")),
                    str(r.get("endDate")),
                    str(r.get("hallLabel")),
                    str(r.get("posterUrl")),
                    str(r.get("status")),
                    toInt(r.get("dday"))));
        }
        return out;
    }

    // ── 전시장(센터) 동적 목록 ────────────────────────────────────────────────────

    public List<HallCenter> getHallCenters() {
        String tenantId = TenantContext.currentTenantId();
        List<Map<String, Object>> rows = mapper.findHallCenters(tenantId);
        List<HallCenter> out = new ArrayList<>();
        for (Map<String, Object> r : rows) {
            Integer center = toInt(r.get("center"));
            if (center == null) {
                continue;
            }
            out.add(new HallCenter(String.valueOf(center), "제" + center + "전시장"));
        }
        return out;
    }

    // ── 역할별 요약 KPI ───────────────────────────────────────────────────────────

    public SummaryResponse getSummary(KintexPrincipal principal) {
        String userId = principal.userId();
        String tenantId = TenantContext.currentTenantId();
        boolean broad = isBroadOversight(principal);
        Map<String, Object> c = mapper.findSummaryCounts(userId, tenantId, broad);
        String role = resolvePrimaryRole(principal);

        List<SummaryTile> tiles = switch (role) {
            case "ADMIN" -> List.of(
                    tile("pendingApprovals", "승인 대기", num(c, "pendingApprovals"), "건", "/work/approval"),
                    tile("ongoingEvents", "진행 중 전시", num(c, "ongoingEvents"), "건", "/schedule"),
                    tile("todayCheckins", "오늘 체크인", num(c, "todayCheckins"), "명", "/visitors"),
                    tile("unpaidInvoices", "미수 청구", num(c, "unpaidInvoices"), "건", "/settlement"));
            case "EXHIBITOR" -> List.of(
                    tile("docsToSubmit", "제출할 서류", num(c, "docsToSubmit"), "건", "/docs"),
                    tile("unpaidInvoices", "미확인 청구", num(c, "unpaidInvoices"), "건", "/settlement"),
                    ddayTile("nearestOpening", "임박 개장", toInt(c.get("nearestOpeningDday")), "/schedule"),
                    tile("pendingApprovals", "미결 결재", num(c, "pendingApprovals"), "건", "/work/approval"));
            case "CONTRACTOR" -> List.of(
                    tile("activeAuctions", "응찰 중 옥션", num(c, "activeAuctions"), "건", "/auctions"),
                    tile("awardedBooths", "수주 부스", num(c, "awardedBooths"), "건", "/contractor/dashboard"),
                    ddayTile("nearestOpening", "임박 개장", toInt(c.get("nearestOpeningDday")), "/schedule"),
                    tile("pendingApprovals", "미결 결재", num(c, "pendingApprovals"), "건", "/work/approval"));
            // ORGANIZER(기본)
            default -> List.of(
                    tile("ongoingEvents", "진행 중 전시", num(c, "ongoingEvents"), "건", "/schedule"),
                    tile("todayCheckins", "오늘 체크인", num(c, "todayCheckins"), "명", "/visitors"),
                    ddayTile("nearestOpening", "임박 개장", toInt(c.get("nearestOpeningDday")), "/schedule"),
                    tile("pendingApprovals", "미결 결재", num(c, "pendingApprovals"), "건", "/work/approval"));
        };
        return new SummaryResponse(role, tiles);
    }

    // ── 헬퍼 ──────────────────────────────────────────────────────────────────────

    /** 감독 권한(전 테넌트 열람) — 홀매니저 또는 전역 ADMIN/MANAGER. */
    private boolean isBroadOversight(KintexPrincipal principal) {
        if (principal.hallManager()) {
            return true;
        }
        String rc = principal.roleCode();
        return rc != null && (rc.equalsIgnoreCase("ADMIN") || rc.equalsIgnoreCase("MANAGER"));
    }

    /** 주 역할 판정: 관리자/홀매니저 → ADMIN, 그 외 행사 역할 우선순위(ORGANIZER > CONTRACTOR > EXHIBITOR). */
    private String resolvePrimaryRole(KintexPrincipal principal) {
        if (isBroadOversight(principal)) {
            return "ADMIN";
        }
        Set<String> roles = Set.copyOf(mapper.findUserEventRoles(principal.userId()));
        if (roles.contains("ORGANIZER")) {
            return "ORGANIZER";
        }
        if (roles.contains("CONTRACTOR")) {
            return "CONTRACTOR";
        }
        if (roles.contains("EXHIBITOR")) {
            return "EXHIBITOR";
        }
        return "ORGANIZER"; // 기본 세트(design.md)
    }

    private SummaryTile tile(String key, String label, Long value, String unit, String route) {
        return new SummaryTile(key, label, value, unit, null, "normal", route);
    }

    /** D-day 칩 타일 — dday 없으면 값 null("—"), D-6 이내 warning·경과 overdue. */
    private SummaryTile ddayTile(String key, String label, Integer dday, String route) {
        String sev = dday == null ? "normal" : dday < 0 ? "overdue" : dday <= 6 ? "warning" : "normal";
        Object value = dday == null ? null : ("D-" + Math.max(dday, 0));
        return new SummaryTile(key, label, value, null, dday, sev, route);
    }

    /**
     * 할 일 심각도. 기한이 있으면 잔여일 기준(경과 overdue · D-3 이내 warning · 그 외 normal).
     * 기한 없는 핫리드 팔로업(lead)은 즉시 조치가 필요한 액션이므로 warning 으로 부각한다(그 외 무기한은 normal).
     */
    private static String severityFor(String type, Integer dday) {
        if (dday == null) {
            return "lead".equals(type) ? "warning" : "normal";
        }
        if (dday < 0) {
            return "overdue";
        }
        return dday <= 3 ? "warning" : "normal";
    }

    /** 정렬용 심각도 랭크(작을수록 상단): 경과 0 · 임박 1 · 일반 2. */
    private static int severityRank(String severity) {
        return switch (severity) {
            case "overdue" -> 0;
            case "warning" -> 1;
            default -> 2;
        };
    }

    private static String normalizeTab(String tab) {
        if (tab == null) {
            return "ongoing";
        }
        String v = tab.trim();
        return switch (v) {
            case "thisMonth", "nextMonth", "ongoing" -> v;
            default -> "ongoing";
        };
    }

    private static Long num(Map<String, Object> m, String key) {
        Object v = m == null ? null : m.get(key);
        if (v instanceof Number n) {
            return n.longValue();
        }
        return 0L;
    }

    private static Integer toInt(Object v) {
        if (v == null) {
            return null;
        }
        if (v instanceof Number n) {
            return n.intValue();
        }
        try {
            return Integer.valueOf(v.toString().trim());
        } catch (NumberFormatException e) {
            return null;
        }
    }

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