from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Any

from app.llm.schemas import (
    ClinicalStructuredModel,
    DocumentoQuirurgicoStructured,
    FacturaStructured,
    HistoriaClinicaStructured,
)
from app.services.document_identity_extraction import extract_document_identity
from app.services.historia_summary import is_valid_historia_resumen
from app.services.patient_name_extraction import sanitize_patient_name_candidate


_DATE_PATTERN = re.compile(r"\b\d{1,2}[/-]\d{1,2}[/-]\d{2,4}\b")
_DATE_TIME_PATTERN = re.compile(
    r"\b\d{1,2}[/-]\d{1,2}[/-]\d{2,4}(?:\s+\d{1,2}:\d{2})?\b|\b\d{1,2}\s+[A-Za-záéíóúñ]+,\s+\d{4}\b",
    re.IGNORECASE,
)
_MONEY_PATTERN = re.compile(r"^\$?\s*[\d.,]+$")
_TABLE_SIGNAL_PATTERN = re.compile(r"\b(cod\.?|cups-cum|cant\.?|precio|total)\b", re.IGNORECASE)
_FACTURA_FIELD_LABEL_PATTERN = re.compile(
    r"\b(prefijo|factura|caso|vencimiento|convenio|fecha ingreso|fecha egreso|paciente|señores)\b",
    re.IGNORECASE,
)
_QX_SIGNAL_PATTERN = re.compile(
    r"\b(diagn[oó]stico|procedimiento|hallazgo|complicaci[oó]n|conclusi[oó]n)\b",
    re.IGNORECASE,
)
_HISTORIA_SECTION_PATTERN = re.compile(
    r"\b(diagn[oó]stic|procedimient|medicament|motivo de consulta|enfermedad actual)\b",
    re.IGNORECASE,
)
_HISTORIA_ANTECEDENT_SECTION_PATTERN = re.compile(
    r"(?im)^\s*ANTECEDENTES(?:\s+(?:PERSONALES|CL[IÍ]NICOS|M[EÉ]DICOS))?\s*$"
)
_HISTORIA_ALLERGY_PATTERN = re.compile(r"\b(?:ALERGIAS?|AL[EÉ]RGICOS?)\s*:", re.IGNORECASE)


@dataclass(frozen=True, slots=True)
class ClinicalExtractionQualityAssessment:
    status: str
    low_confidence: bool
    should_retry: bool
    reason_codes: tuple[str, ...]
    validator_version: str = "v1"

    def to_payload(self) -> dict[str, Any]:
        return {
            "status": self.status,
            "low_confidence": self.low_confidence,
            "should_retry": self.should_retry,
            "reason_codes": list(self.reason_codes),
            "validator_version": self.validator_version,
        }


def assess_clinical_extraction(
    *,
    document_type: str,
    raw_text: str,
    analysis_model: ClinicalStructuredModel,
) -> ClinicalExtractionQualityAssessment:
    reasons = _collect_quality_reasons(
        document_type=str(document_type or "").strip().lower(),
        raw_text=str(raw_text or ""),
        analysis_model=analysis_model,
    )
    low_confidence = bool(reasons)
    return ClinicalExtractionQualityAssessment(
        status="degraded" if low_confidence else "ok",
        low_confidence=low_confidence,
        should_retry=low_confidence,
        reason_codes=tuple(reasons),
    )


def _collect_quality_reasons(
    *,
    document_type: str,
    raw_text: str,
    analysis_model: ClinicalStructuredModel,
) -> list[str]:
    if document_type == "factura" and isinstance(analysis_model, FacturaStructured):
        return _factura_quality_reasons(raw_text, analysis_model)
    if document_type == "historia_clinica" and isinstance(analysis_model, HistoriaClinicaStructured):
        return _historia_quality_reasons(raw_text, analysis_model)
    if document_type == "quirurgico" and isinstance(analysis_model, DocumentoQuirurgicoStructured):
        return _quirurgico_quality_reasons(raw_text, analysis_model)
    return []


def _factura_quality_reasons(raw_text: str, model: FacturaStructured) -> list[str]:
    reasons: list[str] = []
    patient_name = str(model.patient_name or "").strip()
    payer_name = str(model.pagador.aseguradora_eps or "").strip()
    provider_name = str(model.proveedor.nombre_institucion or "").strip()

    if not _is_valid_person_name(patient_name):
        reasons.append("identity_patient_name_invalid")
    if patient_name and patient_name in {payer_name, provider_name}:
        reasons.append("identity_patient_name_conflict")
    if str(model.paciente.numero_identificacion or "").strip() and not str(
        model.paciente.numero_identificacion or ""
    ).strip().replace(".", "").isdigit():
        reasons.append("identity_patient_id_invalid")

    for field_name, value in {
        "fecha_emision": model.datos_factura.fecha_emision,
        "fecha_vencimiento": model.datos_factura.fecha_vencimiento,
        "fecha_ingreso": model.paciente.fecha_ingreso,
        "fecha_egreso": model.paciente.fecha_egreso,
    }.items():
        if _looks_contaminated_date(value):
            reasons.append(f"{field_name}_contaminated")

    for field_name, value in {
        "total_servicios": model.resumen_financiero.total_servicios,
        "descuentos": model.resumen_financiero.descuentos,
        "copagos": model.resumen_financiero.copagos,
        "valor_total_factura": model.resumen_financiero.valor_total_factura,
    }.items():
        if value and not _looks_money(value):
            reasons.append(f"{field_name}_invalid_money")

    has_table_signals = bool(_TABLE_SIGNAL_PATTERN.search(raw_text))
    if has_table_signals and not model.lineas_canonicas:
        reasons.append("missing_canonical_lines")
    return reasons


def _historia_quality_reasons(raw_text: str, model: HistoriaClinicaStructured) -> list[str]:
    reasons: list[str] = []
    identity = extract_document_identity(raw_text, strategy="historia_clinica")
    if "patient_name" not in identity.redacted_identity_fields and not _is_valid_person_name(
        model.patient_name
    ):
        reasons.append("identity_patient_name_invalid")
    if not is_valid_historia_resumen(str(model.resumen_clinico or "")):
        reasons.append("summary_low_quality")
    if _HISTORIA_SECTION_PATTERN.search(raw_text):
        if "diagnost" in raw_text.casefold() and not model.diagnosticos:
            reasons.append("diagnosticos_missing")
        if "proced" in raw_text.casefold() and not model.procedimientos:
            reasons.append("procedimientos_missing")
        if "medic" in raw_text.casefold() and not model.medicamentos:
            reasons.append("medicamentos_missing")
    if _HISTORIA_ANTECEDENT_SECTION_PATTERN.search(raw_text) and not (
        model.antecedentes_estructurados or model.antecedentes
    ):
        reasons.append("antecedentes_missing")
    if _HISTORIA_ALLERGY_PATTERN.search(raw_text) and not any(
        item.categoria == "alergico" for item in model.antecedentes_estructurados
    ):
        reasons.append("alergias_missing")
    return reasons


def _quirurgico_quality_reasons(raw_text: str, model: DocumentoQuirurgicoStructured) -> list[str]:
    reasons: list[str] = []
    if not _is_valid_person_name(model.patient_name):
        reasons.append("identity_patient_name_invalid")
    if _QX_SIGNAL_PATTERN.search(raw_text):
        if not model.diagnosticos:
            reasons.append("diagnosticos_missing")
        if not model.procedimientos:
            reasons.append("procedimientos_missing")
        if not str(model.hallazgos or "").strip():
            reasons.append("hallazgos_missing")
        if not str(model.descripcion_procedimiento or model.procedimiento_principal or "").strip():
            reasons.append("descripcion_missing")
    return reasons


def _is_valid_person_name(value: Any) -> bool:
    sanitized = sanitize_patient_name_candidate(value)
    return bool(sanitized and sanitized.lower() != "desconocido")


def _looks_contaminated_date(value: Any) -> bool:
    text = str(value or "").strip()
    if not text:
        return False
    if not _DATE_TIME_PATTERN.search(text):
        return True
    if _FACTURA_FIELD_LABEL_PATTERN.search(text):
        return True
    return len(text.split()) > 8 and not _DATE_PATTERN.fullmatch(text)


def _looks_money(value: Any) -> bool:
    text = str(value or "").strip()
    if not text:
        return False
    return bool(_MONEY_PATTERN.fullmatch(text.replace(" ", "")))
