package com.zioinfo.kintex.document;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.zioinfo.kintex.document.dto.DocumentReviewDto;
import com.zioinfo.kintex.document.dto.MilestoneDto;
import com.zioinfo.kintex.document.dto.RequiredDocumentDto;
import com.zioinfo.kintex.document.dto.ReviewIssueDto;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * M6 서류·마일스톤 서비스 — 실 테이블 집계로 마일스톤·서류·검수·전이를 조립.
 * dday·sub·progressPct 는 저장값에서 파생(원천 컬럼 최소화). 상태 전이는 화이트리스트 검증.
 */
@Service
public class DocumentService {

    private final DocumentMapper mapper;

    public DocumentService(DocumentMapper mapper) {
        this.mapper = mapper;
    }

    public List<MilestoneDto> getMilestones(String eventId) {
        List<MilestoneDto> out = new ArrayList<>();
        for (Map<String, Object> r : mapper.findMilestones(eventId)) {
            String state = str(r.get("state"));
            out.add(new MilestoneDto(str(r.get("label")), subLabel(state), state, str(r.get("dueDate"))));
        }
        return out;
    }

    public List<RequiredDocumentDto> getDocuments(String eventId) {
        LocalDate today = LocalDate.now();
        List<RequiredDocumentDto> out = new ArrayList<>();
        for (Map<String, Object> r : mapper.findDocuments(eventId)) {
            out.add(toDoc(r, today));
        }
        return out;
    }

    public DocumentReviewDto getReview(String eventId) {
        List<ReviewIssueDto> issues = new ArrayList<>();
        for (Map<String, Object> r : mapper.findReviewIssues(eventId)) {
            issues.add(new ReviewIssueDto(str(r.get("tone")), str(r.get("title")), str(r.get("description"))));
        }
        return new DocumentReviewDto(issues, progressPct(mapper.findDocuments(eventId)));
    }

    /**
     * 서류 상태 전이(SCR-23 임시저장/제출). action=save→draft, submit→submitted.
     * 대상 서류 미존재 → 404. 잘못된 action → 400.
     */
    public RequiredDocumentDto transition(String eventId, String docType, String action) {
        String target = switch (action == null ? "" : action.trim().toLowerCase()) {
            case "save" -> "draft";
            case "submit" -> "submitted";
            default -> null;
        };
        if (target == null) {
            throw new ApiException(ErrorCode.VALIDATION, "action 은 save 또는 submit 이어야 합니다.");
        }
        if (mapper.findDocument(eventId, docType) == null) {
            throw new ApiException(ErrorCode.NOT_FOUND, "해당 신고서류를 찾을 수 없습니다.");
        }
        mapper.updateStatus(eventId, docType, target);
        Map<String, Object> updated = mapper.findDocument(eventId, docType);
        return toDoc(updated, LocalDate.now());
    }

    // ── 파생 ─────────────────────────────────────────────────────────
    private RequiredDocumentDto toDoc(Map<String, Object> r, LocalDate today) {
        String status = str(r.get("status"));
        String dueDate = str(r.get("dueDate"));
        return new RequiredDocumentDto(
                str(r.get("docType")), str(r.get("name")), status, dueDate,
                dday(dueDate, status, today), intVal(r.get("sortOrder")));
    }

    /** 준비중(pending) 제외한 서류 진척률(%). */
    private static int progressPct(List<Map<String, Object>> docs) {
        if (docs == null || docs.isEmpty()) {
            return 0;
        }
        long done = docs.stream().filter(d -> !"pending".equals(String.valueOf(d.get("status")))).count();
        return (int) Math.round(done * 100.0 / docs.size());
    }

    /** 진행 중(pending 아님) 서류에 한해 due_date 까지 남은 일수(과거면 null). */
    private static Integer dday(String dueDate, String status, LocalDate today) {
        if (dueDate == null || "pending".equals(status) || "approved".equals(status)) {
            return null;
        }
        LocalDate due = parseDate(dueDate);
        if (due == null) {
            return null;
        }
        long days = ChronoUnit.DAYS.between(today, due);
        return days < 0 ? null : (int) days;
    }

    private static String subLabel(String state) {
        return switch (state == null ? "" : state) {
            case "done" -> "완료";
            case "active" -> "진행중";
            default -> "대기";
        };
    }

    private static LocalDate parseDate(String s) {
        if (s == null || s.isBlank()) {
            return null;
        }
        try {
            return LocalDate.parse(s);
        } catch (DateTimeParseException e) {
            return null;
        }
    }

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

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