"""Proyecciones comunes de publicación y cola de revisión humana."""

from __future__ import annotations

import json
from collections.abc import Mapping
from hashlib import sha256
from typing import Any

from app.case_epicrisis.domain.publication import (
    PublicationEligibility,
    PublicationProjection,
    PublicationProjectionState,
    evaluate_publication_eligibility,
    is_diagnostic_aid_presentable,
)


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


def _first_source(item: Mapping[str, Any]) -> str:
    evidence = item.get("evidencias") or item.get("evidence") or []
    if isinstance(evidence, list):
        for entry in evidence:
            if isinstance(entry, Mapping):
                value = _text(entry.get("document_type") or entry.get("filename"))
                if value:
                    return value
    return _text(item.get("fuente") or item.get("source"))


def _evidence(item: Mapping[str, Any]) -> list[dict[str, Any]]:
    values = item.get("evidencias") or item.get("evidence") or []
    return [dict(value) for value in values if isinstance(value, Mapping)]


def _stable_hash(value: Any) -> str:
    try:
        payload = json.dumps(value, ensure_ascii=False, sort_keys=True, default=str, separators=(",", ":"))
    except (TypeError, ValueError):
        payload = repr(value)
    return sha256(payload.encode("utf-8")).hexdigest()[:24]


def _evidence_occurrence_id(evidence: Mapping[str, Any], *, index: int) -> str:
    existing = _text(evidence.get("occurrence_id"))
    if existing:
        return existing
    stable_values = {
        key: evidence.get(key)
        for key in (
            "artifact_type",
            "document_uid",
            "document_id",
            "documento_id",
            "filename",
            "documento_nombre",
            "document_type",
            "fuente",
            "page",
            "pagina",
            "section",
            "seccion",
            "original_term",
            "termino_original",
            "excerpt",
            "extracto",
        )
        if evidence.get(key) not in (None, "")
    }
    if not stable_values:
        stable_values = {"index": index}
    return f"occurrence:{_stable_hash(stable_values)}"


def _project_evidences(item: Mapping[str, Any]) -> list[dict[str, Any]]:
    projected: list[dict[str, Any]] = []
    seen_occurrences: set[str] = set()
    for index, raw in enumerate(_evidence(item)):
        evidence = dict(raw)
        evidence.setdefault("occurrence_id", _evidence_occurrence_id(evidence, index=index))
        occurrence_id = _text(evidence.get("occurrence_id"))
        if occurrence_id and occurrence_id in seen_occurrences:
            continue
        if occurrence_id:
            seen_occurrences.add(occurrence_id)
        projected.append(evidence)
    item_occurrence_id = _text(item.get("occurrence_id"))
    if item_occurrence_id and item_occurrence_id not in seen_occurrences:
        projected.insert(0, {"occurrence_id": item_occurrence_id})
    return projected


def _documents_from_evidence(evidences: list[dict[str, Any]], item: Mapping[str, Any]) -> list[str]:
    documents: list[str] = []
    for evidence in evidences:
        value = _text(
            evidence.get("document_uid")
            or evidence.get("document_id")
            or evidence.get("documento_id")
            or evidence.get("filename")
            or evidence.get("documento_nombre")
        )
        if value and value not in documents:
            documents.append(value)
    for key in ("document_uid", "document_id", "documento_id", "documento_nombre", "filename"):
        value = _text(item.get(key))
        if value and value not in documents:
            documents.append(value)
    return documents


def _artifact_identity(item: Mapping[str, Any], *, artifact_type: str, evidences: list[dict[str, Any]]) -> str:
    identifier = _text(
        item.get("artifact_id") or item.get("item_id") or item.get("key") or item.get("candidate_id")
    )
    if identifier:
        return identifier
    if len(evidences) == 1:
        occurrence_id = _text(evidences[0].get("occurrence_id"))
        if occurrence_id:
            return occurrence_id
    return f"legacy:{_stable_hash({'artifact_type': artifact_type, 'item': dict(item), 'evidences': evidences})}"


def _selection_keys(context: Mapping[str, Any] | None, *, artifact_type: str) -> tuple[set[str], set[str]]:
    draft = context.get("pdf_draft") if isinstance(context, Mapping) else None
    draft = draft if isinstance(draft, Mapping) else {}
    included = {
        _text(value)
        for value in draft.get("included_ayudas_diagnosticas_keys") or []
        if _text(value)
    }
    excluded_fields = {
        "diagnostico": ("excluded_diagnostico_keys", "excluded_diagnosticos"),
        "procedimiento": ("excluded_procedimientos",),
        "medicamento": ("excluded_medicamento_keys", "excluded_medicamentos"),
        "ayuda_diagnostica": ("excluded_ayudas_diagnosticas_keys", "excluded_imagenes_diagnosticas"),
    }.get(artifact_type, ())
    excluded = {
        _text(value)
        for field in excluded_fields
        for value in draft.get(field) or []
        if _text(value)
    }
    return included, excluded


def _projection_state(
    *,
    item: Mapping[str, Any],
    artifact_type: str,
    eligibility: PublicationEligibility,
    artifact_id: str,
    context: Mapping[str, Any] | None,
) -> PublicationProjectionState:
    _, excluded = _selection_keys(context, artifact_type=artifact_type)
    if eligibility.normalized_status == "glosado":
        return PublicationProjectionState.GLOSADO
    if eligibility.excluded or artifact_id in excluded:
        return PublicationProjectionState.EXCLUDED
    if eligibility.publishable:
        return PublicationProjectionState.PUBLISHED
    if artifact_type == "ayuda_diagnostica" and is_diagnostic_aid_presentable(item):
        included, _ = _selection_keys(context, artifact_type=artifact_type)
        if artifact_id in included:
            return PublicationProjectionState.SELECTED
        return PublicationProjectionState.SELECTABLE
    return PublicationProjectionState.PENDING_REVIEW


def project_publication_item(
    item: Mapping[str, Any] | None,
    *,
    artifact_type: str,
    context: Mapping[str, Any] | None = None,
    eligibility: PublicationEligibility | None = None,
    order_applied: str = "revision",
) -> dict[str, Any]:
    """Project one item without mutating its source document.

    Existing ``item_id``/``key`` values are the aggregate identity.  Only when
    legacy data lacks those fields is a deterministic hash used.
    """
    raw = dict(item) if isinstance(item, Mapping) else {}
    normalized_type = _text(raw.get("artifact_type") or artifact_type).casefold().replace("-", "_")
    evidences = _project_evidences(raw)
    artifact_id = _artifact_identity(raw, artifact_type=normalized_type, evidences=evidences)
    occurrence_ids = [
        occurrence_id
        for occurrence_id in (_text(evidence.get("occurrence_id")) for evidence in evidences)
        if occurrence_id
    ]
    eligibility = eligibility or evaluate_publication_eligibility(raw, artifact_type=normalized_type)
    state = _projection_state(
        item=raw,
        artifact_type=normalized_type,
        eligibility=eligibility,
        artifact_id=artifact_id,
        context=context,
    )
    reason_review = _text(
        raw.get("razon_revision")
        or raw.get("razon_exclusion")
        or raw.get("reconciliation_reason")
        or raw.get("razon_conciliacion")
        or (eligibility.reason if not eligibility.publishable else "")
    )
    projection = PublicationProjection(
        artifact_id=artifact_id,
        occurrence_id=occurrence_ids[0] if occurrence_ids else "",
        occurrence_ids=list(dict.fromkeys(occurrence_ids)),
        artifact_type=normalized_type,
        source=_first_source(raw),
        documents=_documents_from_evidence(evidences, raw),
        source_status=eligibility.normalized_status or eligibility.status,
        projection_status=state,
        reason_publication=eligibility.reason if eligibility.publishable else "",
        reason_review=reason_review,
        evidences=evidences,
        order_applied=_text(order_applied) or "revision",
    )
    projection_data = projection.model_dump(mode="json")
    # Keep the legacy payload intact while making the common contract available
    # under one stable field and at the top level for simple consumers.
    return raw | {
        "artifact_id": projection.artifact_id,
        "occurrence_id": projection.occurrence_id,
        "occurrence_ids": projection.occurrence_ids,
        "artifact_type": projection.artifact_type,
        "source": projection.source,
        "fuente": projection.source,
        "documents": projection.documents,
        "documentos": projection.documents,
        "source_status": projection.source_status,
        "estado_origen": projection.source_status,
        "projection_status": projection.projection_status.value,
        "estado_proyeccion": projection.projection_status.value,
        "reason_publication": projection.reason_publication,
        "reason_review": projection.reason_review,
        "evidences": projection.evidences,
        "order_applied": projection.order_applied,
        "publication_projection": projection_data,
        "proyeccion_publicacion": projection_data,
    }


def project_publication_items(
    items: Any,
    *,
    artifact_type: str,
    context: Mapping[str, Any] | None = None,
    order_applied: str = "revision",
) -> list[dict[str, Any]]:
    return [
        project_publication_item(
            item,
            artifact_type=artifact_type,
            context=context,
            order_applied=order_applied,
        )
        for item in items or []
        if isinstance(item, Mapping)
    ]


def _projection_sources(context: Mapping[str, Any]) -> tuple[tuple[str, Any], ...]:
    aids = context.get("ayudas_diagnosticas_curadas") or context.get("ayudas_diagnosticas")
    if not isinstance(aids, list):
        from app.case_epicrisis.application.utils.diagnostic_aids import build_ayudas_diagnosticas

        aids = build_ayudas_diagnosticas(dict(context))
    return (
        (
            "diagnostico",
            context.get("diagnosticos_curados")
            or context.get("pdf_primary_diagnosticos")
            or context.get("diagnosticos_consolidados"),
        ),
        (
            "procedimiento",
            context.get("procedimientos_curados")
            or context.get("procedimientos_clinicos")
            or context.get("pdf_primary_procedimientos"),
        ),
        (
            "medicamento",
            context.get("medicamentos_caso")
            or context.get("pdf_primary_medicamentos")
            or context.get("medicamentos"),
        ),
        (
            "ayuda_diagnostica",
            [
                item
                for item in aids
                if isinstance(item, Mapping)
                and _text(item.get("artifact_type") or item.get("tipo") or "ayuda_diagnostica")
                .casefold()
                not in {"medicamento", "medication", "insumo", "suministro", "administrativo"}
            ],
        ),
    )


def build_publication_projections(context: Mapping[str, Any] | None) -> list[dict[str, Any]]:
    """Build the shared four-artifact projection used by all consumers."""
    source = context if isinstance(context, Mapping) else {}
    result: list[dict[str, Any]] = []
    for artifact_type, values in _projection_sources(source):
        result.extend(
            project_publication_items(
                values,
                artifact_type=artifact_type,
                context=source,
                order_applied="revision",
            )
        )
    return result


def _artifact_label(item: Mapping[str, Any], artifact_type: str) -> tuple[str, str, str]:
    if artifact_type == "diagnostico":
        identifier = _text(item.get("item_id") or item.get("key") or item.get("codigo"))
        presentation = _text(
            item.get("concepto_diagnostico_presentacion")
            or item.get("concepto_cie10_oficial")
            or item.get("diagnostico")
            or item.get("descripcion")
            or item.get("description")
        )
        clinical = _text(
            item.get("descripcion_clinica_normalizada")
            or item.get("descripcion")
            or item.get("description")
        )
        return identifier, presentation, clinical
    if artifact_type == "procedimiento":
        identifier = _text(item.get("item_id") or item.get("key") or item.get("codigo_cups"))
        presentation = _text(item.get("descripcion") or item.get("description"))
        return identifier, presentation, presentation
    if artifact_type == "medicamento":
        identifier = _text(item.get("key") or item.get("item_id") or item.get("codigo_referencia"))
        presentation = _text(item.get("nombre") or item.get("medicamento"))
        return identifier, presentation, _text(item.get("texto_original") or presentation)
    identifier = _text(
        item.get("key")
        or item.get("item_id")
        or item.get("documento_id")
        or item.get("canonical_id")
        or item.get("occurrence_id")
    )
    presentation = _text(
        item.get("nombre")
        or item.get("concepto")
        or item.get("descripcion")
        or item.get("canonical_term")
        or item.get("original_term")
    )
    return identifier, presentation, _text(
        item.get("concepto")
        or item.get("descripcion")
        or item.get("excerpt")
        or presentation
    )


def _review_item(
    item: Mapping[str, Any],
    *,
    artifact_type: str,
    eligibility: PublicationEligibility,
    context: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
    projected = project_publication_item(
        item,
        artifact_type=artifact_type,
        context=context,
        eligibility=eligibility,
        order_applied="revision",
    )
    identifier, presentation, clinical = _artifact_label(item, artifact_type)
    identifier = _text(projected.get("artifact_id") or item.get("candidate_id") or identifier)
    reason = _text(item.get("razon_revision") or item.get("razon_exclusion")) or eligibility.reason
    if artifact_type == "ayuda_diagnostica" and _text(item.get("classification_status")) == "unclassified":
        reason = _text(item.get("classification_reason")) or reason
    return projected | {
        "artifact_type": artifact_type,
        "tipo": item.get("tipo") if artifact_type == "ayuda_diagnostica" else artifact_type,
        "artifact_id": identifier,
        "identificador": identifier,
        "status": eligibility.normalized_status or eligibility.status,
        "estado": eligibility.normalized_status or eligibility.status,
        "concepto_presentacion": presentation,
        "descripcion_clinica_normalizada": clinical,
        "texto_original": _text(item.get("original_text") or item.get("texto_original") or clinical),
        "source": _first_source(item),
        "fuente": _first_source(item),
        "evidencias": projected.get("evidencias") or _evidence(item),
        "incidencias": [dict(value) for value in item.get("incidencias") or [] if isinstance(value, Mapping)],
        "alertas": [str(value).strip() for value in item.get("alertas") or [] if str(value).strip()],
        "razon_revision": reason,
        "publication": eligibility.model_dump(mode="json"),
        "validacion_automatica": dict(item.get("automatic") or item.get("validacion_automatica") or {}),
        "actions": list(eligibility.actions),
    }


def _diagnostic_aid_is_invoice(item: Mapping[str, Any]) -> bool:
    if item.get("facturado") is True or _text(item.get("facturado")).casefold() in {"true", "1", "si", "sí"}:
        return True
    sources = {_text(item.get("fuente") or item.get("source")).casefold()}
    sources.update(
        _text(evidence.get("fuente") or evidence.get("document_type")).casefold()
        for evidence in item.get("evidencias") or item.get("evidence") or []
        if isinstance(evidence, Mapping)
    )
    return "factura" in sources


def _diagnostic_aid_is_unclassified(item: Mapping[str, Any]) -> bool:
    status = _text(item.get("classification_status")).casefold().replace("-", "_")
    if status in {"unclassified", "no_clasificado", "sin_clasificar"}:
        return True
    aid_type = _text(item.get("tipo")).casefold().replace("-", "_").replace(" ", "_")
    return bool(aid_type) and aid_type not in {"imagen", "laboratorio", "procedimiento_diagnostico"}


def _audit_diagnostic_aid_item(
    raw: Mapping[str, Any],
    *,
    context: Mapping[str, Any],
) -> dict[str, Any] | None:
    """Build the Auditoría-only aid projection without changing PDF eligibility."""
    if not isinstance(raw, Mapping):
        return None
    source = dict(raw)
    try:
        from app.case_epicrisis.application.utils.diagnostic_aids import project_ayuda_diagnostica

        source = project_ayuda_diagnostica(source)
    except (TypeError, ValueError):
        source = dict(raw)

    unclassified = _diagnostic_aid_is_unclassified(source)
    if _diagnostic_aid_is_invoice(source) and not unclassified:
        return None
    eligibility = evaluate_publication_eligibility(source, artifact_type="ayuda_diagnostica")
    if not eligibility.requires_audit and not unclassified:
        return None
    if eligibility.excluded and not unclassified:
        return None
    if unclassified and eligibility.publishable:
        eligibility = evaluate_publication_eligibility(
            {**source, "status": "pendiente_revision", "estado_interpretacion": "pendiente_revision"},
            artifact_type="ayuda_diagnostica",
        )
    return _review_item(source, artifact_type="ayuda_diagnostica", eligibility=eligibility, context=context)


def build_audit_diagnostic_aid_items(context: Mapping[str, Any] | None) -> list[dict[str, Any]]:
    """Project only aids that belong in the ordinary Auditoría review queue."""
    source = context if isinstance(context, Mapping) else {}
    aids = source.get("ayudas_diagnosticas_curadas") or source.get("ayudas_diagnosticas")
    if not isinstance(aids, list) and any(
        source.get(key) for key in ("historia", "quirurgico", "generico", "historias_adicionales")
    ):
        from app.case_epicrisis.application.utils.diagnostic_aids import build_ayudas_diagnosticas

        aids = build_ayudas_diagnosticas(dict(source))
    if not isinstance(aids, list):
        aids = []
    return [
        projected
        for raw in aids if isinstance(raw, Mapping)
        if (projected := _audit_diagnostic_aid_item(raw, context=source)) is not None
    ]


def build_audit_review_items(context: Mapping[str, Any] | None) -> list[dict[str, Any]]:
    """Build one queue for every non-publishable clinical/external artifact."""
    context = context if isinstance(context, Mapping) else {}
    result: list[dict[str, Any]] = []

    collections = (
        ("diagnostico", context.get("diagnosticos_curados")),
        ("procedimiento", context.get("procedimientos_curados")),
        ("medicamento", context.get("medicamentos_caso") or context.get("medicamentos")),
    )
    for artifact_type, values in collections:
        for raw in values if isinstance(values, list) else []:
            if not isinstance(raw, Mapping):
                continue
            eligibility = evaluate_publication_eligibility(raw, artifact_type=artifact_type)
            if not eligibility.publishable:
                result.append(
                    _review_item(raw, artifact_type=artifact_type, eligibility=eligibility, context=context)
                )

    result.extend(build_audit_diagnostic_aid_items(context))

    rejected_candidates = context.get("ayudas_diagnosticas_rechazadas")
    if not isinstance(rejected_candidates, list):
        from app.case_epicrisis.application.utils.diagnostic_aids import (
            classify_diagnostic_aid_candidates,
        )

        documents = (
            ("historia_clinica", [context.get("historia")]),
            ("quirurgico", [context.get("quirurgico")]),
            ("generico", context.get("generico") or []),
            ("historia_clinica", context.get("historias_adicionales") or []),
        )
        rejected_candidates = [
            candidate
            for source, values in documents
            for document in values
            if isinstance(document, Mapping)
            for candidate in classify_diagnostic_aid_candidates(document, source)["rejected"]
        ]
    queued_candidate_ids = {
        _text(item.get("artifact_id")) for item in result if _text(item.get("artifact_id"))
    }
    for raw in rejected_candidates:
        if not isinstance(raw, Mapping):
            continue
        candidate_id = _text(raw.get("candidate_id"))
        if candidate_id and candidate_id in queued_candidate_ids:
            continue
        artifact_type = _text(raw.get("artifact_type")) or "narrativa_clinica"
        eligibility = evaluate_publication_eligibility(
            {**raw, "status": raw.get("status") or "excluido"},
            artifact_type=artifact_type,
        )
        result.append(_review_item(raw, artifact_type=artifact_type, eligibility=eligibility, context=context))
        if candidate_id:
            queued_candidate_ids.add(candidate_id)

    queued_occurrences = {
        evidence.get("occurrence_id")
        for item in result
        for evidence in item.get("evidencias") or []
        if evidence.get("occurrence_id")
    }
    for raw in context.get("canonical_incidents") or []:
        if not isinstance(raw, Mapping) or raw.get("occurrence_id") in queued_occurrences:
            continue
        artifact_type = _text(raw.get("artifact_type")) or "hallazgo_clinico"
        eligibility = evaluate_publication_eligibility(
            {**raw, "status": raw.get("status") or "por_revisar"},
            artifact_type=artifact_type,
        )
        result.append(_review_item(raw, artifact_type=artifact_type, eligibility=eligibility, context=context))

    external = (
        ("soat", context.get("auto_soat_results")),
        ("soat", context.get("qx_agent_results")),
        ("soat", context.get("manual_soat_results")),
        ("glosa", context.get("medicamentos_glosa")),
    )
    for artifact_type, values in external:
        for raw in values if isinstance(values, list) else []:
            if not isinstance(raw, Mapping):
                continue
            eligibility = evaluate_publication_eligibility(raw, artifact_type=artifact_type)
            result.append(_review_item(raw, artifact_type=artifact_type, eligibility=eligibility, context=context))
    return result


def publication_summary(context: Mapping[str, Any] | None) -> dict[str, int]:
    items = build_audit_review_items(context)
    return {
        "pending_audit": sum(1 for item in items if item["publication"].get("requires_audit")),
        "excluded": sum(1 for item in items if item["publication"].get("excluded")),
        "total": len(items),
    }


__all__ = [
    "build_audit_diagnostic_aid_items",
    "build_audit_review_items",
    "build_publication_projections",
    "project_publication_item",
    "project_publication_items",
    "publication_summary",
]
