"""Server-side readiness checks before final PDF submit (aligned with fe0 applicantHonestyPrerequisites)."""

from __future__ import annotations

import re
import uuid
from datetime import date
from typing import Any, Dict, List, Mapping, Optional

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.initiative_db.application_storage import (
    EVIDENCE_ROLE_RESEARCH,
    EVIDENCE_ROLE_TEXTBOOK,
    EVIDENCE_ROLE_TECHNICAL,
)
from src.initiative_db.models import ApplicationArtifact

FIRST_APPLY_MIN = date(2025, 4, 15)
FIRST_APPLY_MAX = date(2026, 4, 15)


class ApplicationSubmissionNotReadyError(Exception):
    """Tabs JSON or MinIO evidence registry does not satisfy submit rules."""

    def __init__(self, missing: List[str]) -> None:
        self.missing = list(missing)
        msg = "; ".join(self.missing[:10])
        if len(self.missing) > 10:
            msg += " …"
        super().__init__(msg)


def _txt(value: Any) -> str:
    return str(value or "").strip()


def _truthy(value: Any) -> bool:
    if value is True:
        return True
    if isinstance(value, str) and value.strip().lower() in ("true", "1", "yes"):
        return True
    return False


def _parse_dd_mm_yyyy(value: str) -> Optional[date]:
    m = re.fullmatch(r"(\d{2})/(\d{2})/(\d{4})", value.strip())
    if not m:
        return None
    d, mo, y = int(m.group(1)), int(m.group(2)), int(m.group(3))
    try:
        return date(y, mo, d)
    except ValueError:
        return None


def _eff(report: Mapping[str, Any], key: str) -> str:
    eff = report.get("effectiveness")
    if not isinstance(eff, dict):
        return ""
    return _txt(eff.get(key))


def _json_evidence_pdf_present(slot: Any) -> bool:
    if slot is None:
        return False
    if isinstance(slot, dict):
        if _txt(slot.get("serverStorageKey")):
            return True
        try:
            if int(slot.get("size") or 0) > 0:
                return True
        except (TypeError, ValueError):
            pass
    return False


def _digits_cccd(s: str) -> str:
    return "".join(c for c in s if c.isdigit())


def _ban_cam_ket_complete(b: Any) -> bool:
    if not isinstance(b, dict):
        return False
    nk = b.get("ngay_ky") if isinstance(b.get("ngay_ky"), dict) else {}
    if not _txt(b.get("tac_gia_dang_ky")) or not _txt(b.get("don_vi")):
        return False
    if not _txt(b.get("ten_bai_bao")) or not _txt(b.get("nguoi_cam_ket")):
        return False
    cc = _digits_cccd(_txt(b.get("cccd")))
    if len(cc) < 8 or len(cc) > 12:
        return False
    nx = _txt(b.get("nam_xet"))
    if not re.fullmatch(r"\d{4}", nx):
        return False
    vt = b.get("vai_tro") if isinstance(b.get("vai_tro"), dict) else {}
    tc = _truthy(vt.get("tac_gia_chinh"))
    dt = _truthy(vt.get("dong_tac_gia"))
    if int(tc) + int(dt) != 1:
        return False
    ck = b.get("cam_ket") if isinstance(b.get("cam_ket"), dict) else {}
    if not _truthy(ck.get("quyen_so_huu_1")) and not _truthy(ck.get("quyen_so_huu_2")):
        return False
    if not (
        _truthy(ck.get("dong_thuan"))
        and _truthy(ck.get("bai_bao_uy_tin"))
        and _truthy(ck.get("tuan_thu_phap_luat"))
    ):
        return False
    ng = _txt(nk.get("ngay"))
    th = _txt(nk.get("thang"))
    nam = _txt(nk.get("nam"))
    if not ng or not th or not re.fullmatch(r"\d{4}", nam):
        return False
    return True


def _reference_material_honesty_complete(h: Any) -> bool:
    if not isinstance(h, dict):
        return False
    nk = h.get("ngay_ky") if isinstance(h.get("ngay_ky"), dict) else {}
    if (
        not _txt(h.get("tac_gia_dang_ky"))
        or not _txt(h.get("don_vi"))
        or not _txt(h.get("ten_tai_lieu"))
        or not _txt(h.get("nguoi_cam_ket"))
    ):
        return False
    cc = _digits_cccd(_txt(h.get("cccd")))
    if len(cc) < 8 or len(cc) > 12:
        return False
    nx = _txt(h.get("nam_xet"))
    if not re.fullmatch(r"\d{4}", nx):
        return False
    ck = h.get("cam_ket") if isinstance(h.get("cam_ket"), dict) else {}
    if not (
        _truthy(ck.get("thong_tin_trung_thuc"))
        and _truthy(ck.get("trach_nhiem_phap_luat"))
        and _truthy(ck.get("bo_sung_khi_yeu_cau"))
    ):
        return False
    ng = _txt(nk.get("ngay"))
    th = _txt(nk.get("thang"))
    nam = _txt(nk.get("nam"))
    if not ng or not th or not re.fullmatch(r"\d{4}", nam):
        return False
    return True


def _research_domestic_honesty_complete(h: Any) -> bool:
    if not isinstance(h, dict):
        return False
    nk = h.get("ngay_ky") if isinstance(h.get("ngay_ky"), dict) else {}
    if (
        not _txt(h.get("tac_gia_dang_ky"))
        or not _txt(h.get("don_vi"))
        or not _txt(h.get("ten_bai_bao"))
        or not _txt(h.get("nguoi_cam_ket"))
    ):
        return False
    cc = _digits_cccd(_txt(h.get("cccd")))
    if len(cc) < 8 or len(cc) > 12:
        return False
    nx = _txt(h.get("nam_xet"))
    if not re.fullmatch(r"\d{4}", nx):
        return False
    ck = h.get("cam_ket") if isinstance(h.get("cam_ket"), dict) else {}
    if not (
        _truthy(ck.get("thong_tin_trung_thuc"))
        and _truthy(ck.get("trach_nhiem_phap_luat"))
        and _truthy(ck.get("bo_sung_khi_yeu_cau"))
    ):
        return False
    ng = _txt(nk.get("ngay"))
    th = _txt(nk.get("thang"))
    nam = _txt(nk.get("nam"))
    if not ng or not th or not re.fullmatch(r"\d{4}", nam):
        return False
    return True


def _push(ok: bool, gaps: List[str], msg: str) -> None:
    if not ok:
        gaps.append(msg)


def collect_report_tab_gaps(r: Mapping[str, Any]) -> List[str]:
    gaps: List[str] = []
    _push(bool(_txt(r.get("introduction"))), gaps, "Báo cáo: Mở đầu (§1)")
    _push(bool(_txt(r.get("initiativeName"))), gaps, "Báo cáo: Tên sáng kiến")
    _push(bool(_txt(r.get("representativeAuthor"))), gaps, "Báo cáo: Tác giả đại diện")
    _push(bool(_txt(r.get("representativePhone"))), gaps, "Báo cáo: Điện thoại")
    _push(bool(_txt(r.get("representativeEmail"))), gaps, "Báo cáo: Email")
    _push(bool(_txt(r.get("applicationField"))), gaps, "Báo cáo: Lĩnh vực áp dụng")
    _push(bool(_txt(r.get("currentStatus"))), gaps, "Báo cáo: Hiện trạng / giải pháp đã biết (§4.1)")
    _push(bool(_txt(r.get("purpose"))), gaps, "Báo cáo: Mục đích (§4.2)")
    _push(bool(_txt(r.get("implementationSteps"))), gaps, "Báo cáo: Các bước thực hiện")
    _push(bool(_txt(r.get("firstAppliedUnit"))), gaps, "Báo cáo: Đơn vị áp dụng lần đầu")
    _push(bool(_txt(r.get("achievedResult"))), gaps, "Báo cáo: Kết quả thu được")
    _push(bool(_txt(r.get("conditions"))), gaps, "Báo cáo: Điều kiện áp dụng")
    _push(bool(_txt(r.get("novelty"))), gaps, "Báo cáo: Tính mới của sáng kiến")
    _push(bool(_eff(r, "economic")), gaps, "Báo cáo: Hiệu quả kinh tế")
    _push(bool(_eff(r, "teaching")), gaps, "Báo cáo: Hiệu quả công việc / giảng dạy")
    _push(bool(_eff(r, "safety")), gaps, "Báo cáo: Môi trường & an toàn")
    _push(bool(_eff(r, "social")), gaps, "Báo cáo: Nhận thức & xã hội")
    return gaps


def _collect_author_gaps(authors: Any) -> List[str]:
    gaps: List[str] = []
    if not isinstance(authors, list) or len(authors) == 0:
        gaps.append("Đơn: thêm ít nhất một tác giả.")
        return gaps
    for i, a in enumerate(authors):
        if not isinstance(a, dict):
            gaps.append(f"Đơn — tác giả {i + 1}: dữ liệu không hợp lệ")
            continue
        p = f"Đơn — tác giả {i + 1}"
        _push(bool(_txt(a.get("name"))), gaps, f"{p}: họ tên")
        _push(bool(_txt(a.get("dob"))), gaps, f"{p}: ngày sinh")
        _push(bool(_txt(a.get("workplace"))), gaps, f"{p}: nơi công tác")
        _push(bool(_txt(a.get("title"))), gaps, f"{p}: chức danh")
        _push(bool(_txt(a.get("qualification"))), gaps, f"{p}: trình độ")
    total = 0
    for a in authors:
        if isinstance(a, dict):
            try:
                total += int(float(a.get("contributionPercent") or 0))
            except (TypeError, ValueError):
                pass
    _push(total == 100, gaps, "Đơn: tổng % đóng góp của các tác giả phải bằng 100%.")
    return gaps


def _classification_pdf_ok(
    classification: str,
    application: Mapping[str, Any],
    evidence_flags: Mapping[str, bool],
) -> tuple[bool, List[str]]:
    """Returns (all_ok, gap_messages)."""
    gaps: List[str] = []

    def research_ok() -> bool:
        return bool(evidence_flags.get("research")) or _json_evidence_pdf_present(application.get("researchEvidenceFile"))

    def textbook_ok() -> bool:
        return bool(evidence_flags.get("textbook")) or _json_evidence_pdf_present(application.get("textbookEvidenceFile"))

    def technical_ok() -> bool:
        return bool(evidence_flags.get("technical")) or _json_evidence_pdf_present(application.get("technicalEvidenceFile"))

    if classification == "technical":
        _push(technical_ok(), gaps, "Đơn (Nhóm 1): cần tệp PDF minh chứng kỹ thuật / văn bản đơn vị (đã tải lên máy chủ).")
        return (len(gaps) == 0, gaps)

    if classification == "textbook":
        kind = _txt(application.get("textbookEvidenceKind"))
        _push(bool(kind), gaps, "Đơn (Nhóm 2.2): chọn loại minh chứng (xuất sắc / tài liệu tham khảo).")
        _push(textbook_ok(), gaps, "Đơn (Nhóm 2.2): cần tệp PDF minh chứng (Quyết định xuất bản…).")
        if kind == "book":
            _push(_ban_cam_ket_complete(application.get("banCamKet")), gaps, "Đơn (Nhóm 2.2 — sách/giáo trình): hoàn thành bản cam kết tác giả.")
        if kind == "reference":
            _push(
                _reference_material_honesty_complete(application.get("referenceMaterialHonesty")),
                gaps,
                "Đơn (Nhóm 2.2 — tài liệu tham khảo): hoàn thành biểu xác minh trung thực.",
            )
        return (len(gaps) == 0, gaps)

    if classification == "research":
        rek = _txt(application.get("researchEvidenceKind"))
        _push(bool(rek), gaps, "Đơn (Nhóm 2.1): chọn loại minh chứng (tạp chí / poster…).")
        _push(research_ok(), gaps, "Đơn (Nhóm 2.1): cần tệp PDF minh chứng bài báo / poster.")
        if rek == "international":
            ban_ok = _ban_cam_ket_complete(application.get("banCamKet"))
            legacy = bool(_txt(application.get("internationalJournalDeclaration")))
            _push(ban_ok or legacy, gaps, "Đơn (Nhóm 2.1 — quốc tế): hoàn thành bản cam kết tác giả hoặc tuyên bố tạp chí.")
        if rek == "domestic":
            ban_ok_dom = _ban_cam_ket_complete(application.get("banCamKet"))
            _push(
                ban_ok_dom
                or _research_domestic_honesty_complete(application.get("researchDomesticHonesty")),
                gaps,
                "Đơn (Nhóm 2.1 — trong nước): hoàn thành biểu xác nhận bài báo trong nước.",
            )
        return (len(gaps) == 0, gaps)

    return (True, [])


def collect_application_tab_gaps(
    application: Mapping[str, Any],
    evidence_flags: Mapping[str, bool],
) -> List[str]:
    gaps: List[str] = []
    unit_ok = bool(_txt(application.get("unitName")))
    if not unit_ok:
        authors0 = application.get("authors")
        if isinstance(authors0, list) and len(authors0) > 0 and isinstance(authors0[0], dict):
            unit_ok = bool(_txt(authors0[0].get("workplace")))
    _push(unit_ok, gaps, "Đơn: Tên đơn vị")
    gaps.extend(_collect_author_gaps(application.get("authors")))
    _push(bool(_txt(application.get("initiativeName"))), gaps, "Đơn: Tên sáng kiến")
    _push(bool(_txt(application.get("investorName"))), gaps, "Đơn: Chủ đầu tư")
    _push(bool(_txt(application.get("applicationField"))), gaps, "Đơn: Lĩnh vực áp dụng")
    fad_raw = _txt(application.get("firstApplyDate"))
    fad = _parse_dd_mm_yyyy(fad_raw) if fad_raw else None
    _push(fad is not None, gaps, "Đơn: Ngày áp dụng lần đầu (định dạng dd/mm/yyyy)")
    if fad is not None and (fad < FIRST_APPLY_MIN or fad > FIRST_APPLY_MAX):
        gaps.append("Đơn: Ngày áp dụng lần đầu phải từ 15/04/2025 đến 15/04/2026.")

    ic = application.get("initiativeClassification")
    ic_str = _txt(ic) if ic is not None else ""
    _push(ic_str != "", gaps, "Đơn: Phân loại sáng kiến (Nhóm 1 / 2.1 / 2.2)")
    if ic_str:
        _, cgaps = _classification_pdf_ok(ic_str, application, evidence_flags)
        gaps.extend(cgaps)

    _push(bool(_txt(application.get("contentSummary"))), gaps, "Đơn: Nội dung của sáng kiến (§4)")
    _push(bool(_txt(application.get("conditions"))), gaps, "Đơn: Điều kiện áp dụng")
    _push(bool(_txt(application.get("authorEvaluation"))), gaps, "Đơn: Đánh giá lợi ích (tác giả)")
    _push(bool(_txt(application.get("trialEvaluation"))), gaps, "Đơn: Đánh giá (đơn vị áp dụng thử)")

    ss = application.get("supportStaff")
    if isinstance(ss, list) and len(ss) > 0:
        for i, row in enumerate(ss):
            if not isinstance(row, dict):
                gaps.append(f"Đơn — người hỗ trợ {i + 1}: dữ liệu không hợp lệ")
                continue
            p = f"Đơn — người hỗ trợ {i + 1}"
            _push(bool(_txt(row.get("name"))), gaps, f"{p}: họ tên")
            _push(bool(_txt(row.get("dob"))), gaps, f"{p}: ngày sinh")
            _push(bool(_txt(row.get("workplace"))), gaps, f"{p}: nơi công tác")
            _push(bool(_txt(row.get("title"))), gaps, f"{p}: chức danh")
            _push(bool(_txt(row.get("qualification"))), gaps, f"{p}: trình độ")
            _push(bool(_txt(row.get("supportContent"))), gaps, f"{p}: nội dung hỗ trợ")

    sd = application.get("submissionDay")
    sm = application.get("submissionMonth")
    _push(sd is not None and _txt(sd) != "", gaps, "Đơn: Ngày ký (ngày)")
    _push(sm is not None and _txt(sm) != "", gaps, "Đơn: Ngày ký (tháng)")
    _push(bool(_txt(application.get("submissionYear"))), gaps, "Đơn: Ngày ký (năm)")
    return gaps


def collect_submission_readiness_gaps(
    tabs: Mapping[str, Any],
    evidence_flags: Mapping[str, bool],
) -> List[str]:
    """Validate merged draft tabs + Postgres/MinIO evidence registry."""
    gaps: List[str] = []
    raw_r = tabs.get("report")
    raw_a = tabs.get("application")
    raw_c = tabs.get("contribution")
    if not isinstance(raw_r, dict) or not isinstance(raw_a, dict) or not isinstance(raw_c, dict):
        gaps.append("Bản nháp thiếu dữ liệu một hoặc nhiều tab (báo cáo / đơn / xác nhận đóng góp).")

    report = raw_r if isinstance(raw_r, dict) else {}
    application = raw_a if isinstance(raw_a, dict) else {}
    contribution = raw_c if isinstance(raw_c, dict) else {}

    gaps.extend(collect_report_tab_gaps(report))
    gaps.extend(collect_application_tab_gaps(application, evidence_flags))

    _push(_truthy(report.get("honestyConfirmed")), gaps, "Báo cáo: cần tick ô cam kết trung thực ở cuối tab Báo cáo.")
    _push(_truthy(application.get("honestyConfirmed")), gaps, "Đơn: cần tick ô cam kết trung thực ở cuối tab Đơn.")
    _push(
        _truthy(contribution.get("digitalSignatureConfirmed")),
        gaps,
        "Xác nhận đóng góp: cần tick ô cam kết trung thực ở tab Xác nhận tỷ lệ đóng góp trước khi gửi.",
    )
    return gaps


async def fetch_evidence_presence_flags(session: AsyncSession, initiative_id: uuid.UUID) -> Dict[str, bool]:
    stmt = select(ApplicationArtifact.role, ApplicationArtifact.storage_uri).where(
        ApplicationArtifact.initiative_id == initiative_id,
        ApplicationArtifact.role.in_(
            (
                EVIDENCE_ROLE_RESEARCH,
                EVIDENCE_ROLE_TEXTBOOK,
                EVIDENCE_ROLE_TECHNICAL,
            )
        ),
    )
    rows = (await session.execute(stmt)).all()
    out = {"research": False, "textbook": False, "technical": False}
    for role, uri in rows:
        if not _txt(uri):
            continue
        if role == EVIDENCE_ROLE_RESEARCH:
            out["research"] = True
        elif role == EVIDENCE_ROLE_TEXTBOOK:
            out["textbook"] = True
        elif role == EVIDENCE_ROLE_TECHNICAL:
            out["technical"] = True
    return out
