from __future__ import annotations

"""Helper coverage cho remediation planning."""


import json
from pathlib import Path
from typing import Any

from app.models.schemas import (
    DossierCoverageDocumentItem,
    DossierCoverageProjectItem,
    DossierCoverageResponse,
)


def load_coverage_manifest(manifest_path: Path) -> dict[str, Any]:
    """Đọc coverage manifest."""
    if not manifest_path.exists():
        raise ValueError(
            f"Không tìm thấy coverage manifest tại {manifest_path}; hãy chạy ingest coverage trước."
        )
    try:
        return json.loads(manifest_path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        raise ValueError(f"Coverage manifest không hợp lệ: {exc}") from exc


def load_coverage_index_with_status(
    manifest_path: Path,
    ensure_list: Any,
) -> tuple[dict[str, dict[str, Any]], bool, str | None]:
    """Đọc index coverage an toàn."""
    try:
        manifest = load_coverage_manifest(manifest_path)
    except ValueError as exc:
        return (
            {},
            True,
            f"Không thể nạp coverage manifest; remediation planning đang dùng fallback an toàn. Chi tiết: {exc}",
        )
    index: dict[str, dict[str, Any]] = {}
    for item in ensure_list(manifest.get("projects")):
        project_code = str(item.get("project_code") or "").strip()
        if project_code:
            index[project_code] = item
    return index, False, None


def coverage_project_payload(
    item: dict[str, Any], ensure_list: Any, string_int_dict: Any
) -> DossierCoverageProjectItem:
    """Chuẩn hóa project coverage."""
    documents = [
        DossierCoverageDocumentItem(
            relative_path=str(document.get("relative_path") or ""),
            source_filename=str(document.get("source_filename") or ""),
            document_type=str(document.get("document_type") or "unknown"),
            doc_stage=str(document.get("doc_stage") or "unclassified"),
            classification_source=str(
                document.get("classification_source") or "unknown"
            ),
            classification_confidence=float(
                document.get("classification_confidence") or 0.0
            ),
        )
        for document in ensure_list(item.get("documents"))
    ]
    return DossierCoverageProjectItem(
        project_code=str(item.get("project_code") or ""),
        project_name=str(item.get("project_name") or ""),
        project_root=str(item.get("project_root") or "") or None,
        project_root_exists=bool(item.get("project_root_exists")),
        total_files=int(item.get("total_files") or 0),
        extractable_files=int(item.get("extractable_files") or 0),
        classified_files=int(item.get("classified_files") or 0),
        unknown_files=int(item.get("unknown_files") or 0),
        knowledge_files_ingested=int(item.get("knowledge_files_ingested") or 0),
        knowledge_chunks_ingested=int(item.get("knowledge_chunks_ingested") or 0),
        document_type_counts=string_int_dict(item.get("document_type_counts")),
        missing_document_types=[
            str(x) for x in item.get("missing_document_types", []) if str(x)
        ],
        sample_unknown_files=[
            str(x) for x in item.get("sample_unknown_files", []) if str(x)
        ],
        sample_skipped_files=[
            str(x) for x in item.get("sample_skipped_files", []) if str(x)
        ],
        missing_files=[str(x) for x in item.get("missing_files", []) if str(x)],
        ingested_document_types=[
            str(x) for x in item.get("ingested_document_types", []) if str(x)
        ],
        stages=[str(x) for x in item.get("stages", []) if str(x)],
        documents=documents,
        readiness_gaps=derive_manifest_readiness_gaps(item),
    )


def derive_manifest_readiness_gaps(item: dict[str, Any]) -> list[str]:
    """Suy ra readiness gaps."""
    gaps: list[str] = []
    missing_document_types = [
        str(x) for x in item.get("missing_document_types", []) if str(x)
    ]
    unknown_files = int(item.get("unknown_files") or 0)
    missing_files = [str(x) for x in item.get("missing_files", []) if str(x)]
    if missing_document_types:
        gaps.append(
            "Thiếu document types trọng yếu: "
            + ", ".join(sorted(missing_document_types)[:8])
        )
    if unknown_files:
        gaps.append(f"Còn {unknown_files} file chưa phân loại trong kho hồ sơ.")
    if missing_files:
        gaps.append(
            f"Có {len(missing_files)} file được cấu hình nhưng không còn tồn tại trên đĩa."
        )
    return gaps


def build_fallback_project_coverage_response(
    *,
    project_code: str,
    manifest_path: Path,
    message: str,
    ensure_list: Any,
    string_int_dict: Any,
) -> DossierCoverageResponse:
    """Tạo coverage fallback."""
    payload = coverage_project_payload(
        {
            "project_code": project_code,
            "project_name": "",
            "project_root": None,
            "project_root_exists": False,
            "total_files": 0,
            "extractable_files": 0,
            "classified_files": 0,
            "unknown_files": 0,
            "knowledge_files_ingested": 0,
            "knowledge_chunks_ingested": 0,
            "document_type_counts": {},
            "missing_document_types": [],
            "sample_unknown_files": [],
            "sample_skipped_files": [],
            "missing_files": [],
            "ingested_document_types": [],
            "stages": [],
            "documents": [],
        },
        ensure_list,
        string_int_dict,
    )
    payload.readiness_gaps.append(
        "Coverage manifest chưa khả dụng; đang dùng fallback coverage rỗng an toàn."
    )
    return DossierCoverageResponse(
        project_code=project_code,
        ingest_batch=None,
        manifest_path=str(manifest_path),
        generated_at=None,
        project=payload,
        source="fallback",
        used_fallback=True,
        message=message,
    )
