from __future__ import annotations

import re
import unicodedata
from typing import Any

from app.llm.schemas import FacturaStructured


_DATE_LINE_PATTERN = re.compile(r"^\d{2}-\d{2}-\d{4}$")
_QUANTITY_PATTERN = re.compile(r"^\d+(?:[.,]\d+)?$")
_MONEY_PATTERN = re.compile(r"^\$\s*[\d.,]+$")
_CODE_PATTERN = re.compile(r"^[A-Z0-9][A-Z0-9.-]{2,}$", re.IGNORECASE)
_NIT_PATTERN = re.compile(r"\bNIT:\s*([0-9.\-]+)", re.IGNORECASE)
_CASE_PATTERN = re.compile(r"\bCaso\s+No\.\s*([A-Z0-9-]+)", re.IGNORECASE)
_PATIENT_ID_PATTERN = re.compile(r"\bCC:\s*([0-9.]+)", re.IGNORECASE)
_PREFIJO_FACTURA_PATTERN = re.compile(
    r"Prefijo\s+No\.\s*Factura\s+([A-Z0-9-]+)\s+([A-Z0-9-]+)",
    re.IGNORECASE | re.DOTALL,
)
_PAYER_PATTERN = re.compile(r"^([0-9.]+)\s*-\s*(.+)$")
_SERVICE_HEADER_PATTERN = re.compile(r"tipo de servicio y/?o mercancia", re.IGNORECASE)
_CITY_AND_DATE_PATTERN = re.compile(
    r"^(?P<city>.+?),\s*(?P<date>\d{1,2}\s+[A-Za-záéíóúñ]+,\s+\d{4}\s+\d{1,2}:\d{2}(?:\s*[ap]\.m\.)?)$",
    re.IGNORECASE,
)
_VENCIMIENTO_PATTERN = re.compile(r"Vencimiento:\s*(.+?)\s+Son:", re.IGNORECASE | re.DOTALL)
_VALOR_LETRAS_PATTERN = re.compile(r"Son:\s*(.+?)\s+EMISOR:", re.IGNORECASE | re.DOTALL)
_AM_PM_FIX_PATTERN = re.compile(r"(\d{1,2}:\d{2})\s*\n+\s*([ap])\.m\.", re.IGNORECASE)
_SECTION_START_LABELS = {
    "señores:",
    "dirección:",
    "convenio:",
    "fecha ingreso:",
    "paciente:",
    "fecha egreso:",
    "cc:",
    "por:",
    "no.autorizacion:",
}
_SKIP_SERVICE_CONTAINS = (
    "número de autorización de facturación electrónica",
    "factura electrónica de venta",
    "contrubuyentes de renta",
    "nit:",
    "caso no.",
    "fecha factura:",
    "señores:",
    "dirección:",
    "convenio:",
    "fecha ingreso:",
    "paciente:",
    "fecha egreso:",
    "cc:",
    "por:",
    "no.autorizacion:",
    "cups-cum",
    "tipo de servicio y/o mercancia",
    "tipo de servicio y/o mercancia",
    "proveedor tecnologico:",
    "página ",
    "facture s.a.s",
    "applicationresponse",
    "identificador evento:",
    "fecha evento:",
    "codigo evento:",
    "evento:",
    "aheeeha",
    "bnfffnb",
    "* principal:",
)
_LAB_KEYWORDS = (
    "hemograma",
    "cuadro hematico",
    "cuadro hemático",
    "protrombina",
    "tromboplastina",
    "leucograma",
)
_IMAGING_KEYWORDS = (
    "radiografia",
    "radiografía",
    "rx ",
    "rayos x",
    "fluoroscopia",
    "fluoroscop",
    "intensificador de imagen",
    "tomografia",
    "tomografía",
    "ecografia",
    "ecografía",
)
_HOSPITALIZATION_KEYWORDS = (
    "habitacion",
    "habitación",
    "cama",
    "estancia",
)
_HONORARIOS_KEYWORDS = (
    "honorarios",
    "anestesiologo",
    "anestesiólogo",
    "ayudantia",
    "ayudantía",
    "cirujano",
    "ginecoobstetra",
)
_OTHER_SERVICE_KEYWORDS = (
    "sala de observacion",
    "sala de observación",
    "sala observacion",
    "derechos de sala",
    "derecho sala",
    "materiales",
    "material",
    "sutura",
    "curacion",
    "curación",
    "jeringa",
    "cateter",
    "catéter",
    "abocat",
    "buretrol",
    "equipo de venoclisis",
    "equipo de venoclisis",
    "equipo",
    "venoclisis",
    "oxigeno",
    "oxígeno",
    "gases anestesicos",
    "gases anestésicos",
    "dispositivo",
)
_MEDICATION_KEYWORDS = (
    "acetaminofen",
    "acetaminofen",
    "cefalexina",
    "cefazolina",
    "cloruro de sodio",
    "solucion salina",
    "solución salina",
    "dipirona",
    "ketorolaco",
    "omeprazol",
    "tramadol",
    "dexketoprofeno",
    "capsula",
    "cápsula",
    "capsulas",
    "cápsulas",
    "tabletas",
    "tableta",
    "solucion inyectable",
    "solución inyectable",
    "ampollas",
    "ampolla",
    "frasco vial",
    "bolsa x",
)


def _clean_text(value: Any) -> str:
    return re.sub(r"\s+", " ", str(value or "")).strip()


def _normalize_ascii(value: Any) -> str:
    normalized = unicodedata.normalize("NFKD", str(value or ""))
    return "".join(char for char in normalized if not unicodedata.combining(char)).lower()


def _digits(value: Any) -> str:
    return re.sub(r"\D", "", str(value or ""))


def _normalize_invoice_text(raw_text: str) -> str:
    text = str(raw_text or "").replace("\x0c", "\n")
    return _AM_PM_FIX_PATTERN.sub(r"\1 \2.m.", text)


def _coalesce(*values: Any) -> str:
    for value in values:
        text = _clean_text(value)
        if text:
            return text
    return ""


def _extract_label_value(lines: list[str], label: str, *, max_lines: int = 2) -> str:
    normalized_label = _normalize_ascii(label)
    for index, line in enumerate(lines):
        normalized_line = _normalize_ascii(line)
        if not normalized_line.startswith(normalized_label):
            continue
        remainder = _clean_text(line[len(label) :].lstrip(": "))
        values: list[str] = [remainder] if remainder else []
        probe = index + 1
        while probe < len(lines) and len(values) < max_lines:
            candidate = _clean_text(lines[probe])
            normalized_candidate = _normalize_ascii(candidate)
            if not candidate:
                probe += 1
                if values:
                    break
                continue
            if normalized_candidate in _SECTION_START_LABELS or candidate.endswith(":"):
                break
            values.append(candidate)
            probe += 1
        return _clean_text(" ".join(values))
    return ""


def _extract_amount_after_label(lines: list[str], label: str, *, max_lookahead: int = 5) -> str:
    for index, line in enumerate(lines):
        if not _normalize_ascii(line).startswith(_normalize_ascii(label)):
            continue
        inline = _clean_text(line[len(label) :].lstrip(": "))
        if _MONEY_PATTERN.match(inline):
            return inline
        for probe in range(index + 1, min(len(lines), index + 1 + max_lookahead)):
            candidate = _clean_text(lines[probe])
            if _MONEY_PATTERN.match(candidate):
                return candidate
            if candidate.endswith(":"):
                break
    return ""


def _extract_provider_name(lines: list[str]) -> str:
    for index, line in enumerate(lines):
        if _normalize_ascii(line) != "factura electronica de venta":
            continue
        for probe in range(index - 1, -1, -1):
            candidate = _clean_text(lines[probe])
            normalized = _normalize_ascii(candidate)
            if not candidate:
                continue
            if "numero de autorizacion de facturacion electronica" in normalized:
                continue
            if candidate.endswith(":"):
                continue
            return candidate
    return ""


def _extract_match_text(pattern: re.Pattern[str], text: str) -> str:
    match = pattern.search(text)
    return _clean_text(match.group(1)) if match else ""


def _extract_invoice_header_values(compact_text: str) -> tuple[str, str]:
    match = _PREFIJO_FACTURA_PATTERN.search(compact_text)
    if not match:
        return "", ""
    return _clean_text(match.group(1)), _clean_text(match.group(2))


def _extract_city_and_issue_date(lines: list[str]) -> tuple[str, str]:
    raw = _extract_label_value(lines, "Fecha Factura:", max_lines=2)
    if not raw:
        return "", ""
    match = _CITY_AND_DATE_PATTERN.match(raw)
    if not match:
        return "", raw
    return _clean_text(match.group("city")), _clean_text(match.group("date"))


def _extract_payer_fields(lines: list[str]) -> tuple[str, str]:
    raw = _extract_label_value(lines, "Señores:", max_lines=1)
    if not raw:
        return "", ""
    match = _PAYER_PATTERN.match(raw)
    if not match:
        return raw, ""
    return _clean_text(match.group(2)), _clean_text(match.group(1))


def _extract_vencimiento(compact_text: str) -> str:
    match = _VENCIMIENTO_PATTERN.search(compact_text)
    return _clean_text(match.group(1)) if match else ""


def _extract_valor_en_letras(compact_text: str) -> str:
    match = _VALOR_LETRAS_PATTERN.search(compact_text)
    return _clean_text(match.group(1)) if match else ""


def _looks_like_service_noise(line: str) -> bool:
    normalized = _normalize_ascii(line)
    return any(token in normalized for token in _SKIP_SERVICE_CONTAINS)


def _looks_like_reference_code(line: str) -> bool:
    cleaned = _clean_text(line)
    return bool(_CODE_PATTERN.match(cleaned) and " " not in cleaned)


def _classify_service_section(description: str) -> str:
    normalized = _normalize_ascii(description)
    if any(keyword in normalized for keyword in _LAB_KEYWORDS):
        return "examenes_laboratorio"
    if any(keyword in normalized for keyword in _IMAGING_KEYWORDS):
        return "imagenologia"
    if any(keyword in normalized for keyword in _HOSPITALIZATION_KEYWORDS):
        return "hospitalizacion"
    if any(keyword in normalized for keyword in _HONORARIOS_KEYWORDS):
        return "honorarios_medicos"
    if any(keyword in normalized for keyword in _OTHER_SERVICE_KEYWORDS):
        return "otros_servicios"
    if any(keyword in normalized for keyword in _MEDICATION_KEYWORDS):
        return "medicamentos"
    return "procedimientos_quirurgicos"


def _build_service_item(
    *,
    fecha_servicio: str,
    codigo_facturacion: str,
    codigo_referencia: str,
    descripcion: str,
    cantidad: str,
    valor_unitario: str,
    total: str,
) -> tuple[str, dict[str, str]]:
    section = _classify_service_section(descripcion)
    base = {
        "fecha_servicio": fecha_servicio,
        "codigo_facturacion": codigo_facturacion,
        "codigo_referencia": codigo_referencia,
        "cantidad": cantidad,
        "valor_unitario": valor_unitario,
        "total": total,
    }
    if section == "procedimientos_quirurgicos":
        return section, {
            **base,
            "concepto": codigo_facturacion or "No especificado",
            "codigo_cups": codigo_referencia or "",
            "descripcion": descripcion,
        }
    if section == "examenes_laboratorio":
        return section, {
            **base,
            "prueba": codigo_facturacion or descripcion,
            "descripcion": descripcion,
        }
    if section == "imagenologia":
        return section, {
            **base,
            "estudio": codigo_facturacion or descripcion,
            "descripcion": descripcion,
        }
    if section == "hospitalizacion":
        return section, {
            **base,
            "habitacion": descripcion,
            "dias": cantidad,
            "tarifa": valor_unitario,
            "descripcion": descripcion,
        }
    if section == "honorarios_medicos":
        return section, {
            **base,
            "rol": codigo_facturacion or descripcion,
            "profesional": descripcion,
        }
    if section == "medicamentos":
        return section, {
            **base,
            "medicamento": descripcion,
            "dosis": codigo_referencia or "",
        }
    return section, {
        **base,
        "descripcion": descripcion,
    }


def _parse_service_lines(lines: list[str]) -> dict[str, list[dict[str, str]]]:
    services = {
        "procedimientos_quirurgicos": [],
        "examenes_laboratorio": [],
        "imagenologia": [],
        "hospitalizacion": [],
        "honorarios_medicos": [],
        "medicamentos": [],
        "otros_servicios": [],
    }

    start_index = None
    end_index = None
    for index, line in enumerate(lines):
        if _SERVICE_HEADER_PATTERN.search(line):
            start_index = index + 1
            break
    if start_index is None:
        return services
    for index in range(start_index, len(lines)):
        if _normalize_ascii(lines[index]).startswith("vencimiento:"):
            end_index = index
            break
    service_lines = [_clean_text(line) for line in lines[start_index:end_index] if _clean_text(line)]

    filtered_lines = [line for line in service_lines if not _looks_like_service_noise(line)]
    cursor = 0
    while cursor < len(filtered_lines):
        if not _DATE_LINE_PATTERN.match(filtered_lines[cursor]):
            cursor += 1
            continue

        fecha_servicio = filtered_lines[cursor]
        cursor += 1
        if cursor >= len(filtered_lines):
            break
        codigo_facturacion = filtered_lines[cursor] if _looks_like_reference_code(filtered_lines[cursor]) else ""
        if not codigo_facturacion:
            continue
        cursor += 1

        codigo_referencia = ""
        if cursor < len(filtered_lines) and _looks_like_reference_code(filtered_lines[cursor]):
            codigo_referencia = filtered_lines[cursor]
            cursor += 1

        descripcion_parts: list[str] = []
        while cursor < len(filtered_lines) and not _QUANTITY_PATTERN.match(filtered_lines[cursor]):
            if _DATE_LINE_PATTERN.match(filtered_lines[cursor]):
                break
            descripcion_parts.append(filtered_lines[cursor])
            cursor += 1

        if not descripcion_parts or cursor >= len(filtered_lines):
            continue
        cantidad = filtered_lines[cursor]
        cursor += 1

        if cursor >= len(filtered_lines) or not _MONEY_PATTERN.match(filtered_lines[cursor]):
            continue
        valor_unitario = filtered_lines[cursor]
        cursor += 1

        if cursor >= len(filtered_lines) or not _MONEY_PATTERN.match(filtered_lines[cursor]):
            continue
        total = filtered_lines[cursor]
        cursor += 1

        descripcion = _clean_text(" ".join(descripcion_parts))
        section, item = _build_service_item(
            fecha_servicio=fecha_servicio,
            codigo_facturacion=codigo_facturacion,
            codigo_referencia=codigo_referencia,
            descripcion=descripcion,
            cantidad=cantidad,
            valor_unitario=valor_unitario,
            total=total,
        )
        services[section].append(item)

    return services


def parse_factura_text(raw_text: str) -> dict[str, Any]:
    normalized_text = _normalize_invoice_text(raw_text)
    lines = [_clean_text(line) for line in normalized_text.splitlines()]
    compact_text = _clean_text(normalized_text)
    prefijo, numero_factura = _extract_invoice_header_values(compact_text)
    city_department, fecha_emision = _extract_city_and_issue_date(lines)
    payer_name, payer_nit = _extract_payer_fields(lines)

    return {
        "nombre_paciente": _extract_label_value(lines, "Paciente:", max_lines=1),
        "proveedor": {
            "nombre_institucion": _extract_provider_name(lines),
            "nit": _extract_match_text(_NIT_PATTERN, normalized_text),
            "direccion": _extract_provider_address(lines),
            "ciudad_departamento": city_department,
        },
        "informacion_factura": {
            "numero_factura": numero_factura,
            "prefijo": prefijo,
            "fecha_emision": fecha_emision,
            "numero_caso": _extract_match_text(_CASE_PATTERN, normalized_text),
            "fecha_vencimiento": _extract_vencimiento(compact_text),
        },
        "pagador": {
            "aseguradora_eps": payer_name,
            "nit_pagador": payer_nit,
            "tipo_convenio": _extract_label_value(lines, "Convenio:", max_lines=1),
            "numero_autorizacion": _extract_label_value(lines, "No.Autorizacion:", max_lines=1),
        },
        "informacion_paciente": {
            "nombre_completo": _extract_label_value(lines, "Paciente:", max_lines=1),
            "numero_identificacion": _extract_match_text(_PATIENT_ID_PATTERN, normalized_text),
            "fecha_ingreso": _extract_label_value(lines, "Fecha Ingreso:", max_lines=1),
            "fecha_egreso": _extract_label_value(lines, "Fecha Egreso:", max_lines=1),
            "estancia_hospitalaria": "",
        },
        "servicios_procedimientos": _parse_service_lines(lines),
        "analisis_financiero": {
            "total_servicios": _extract_amount_after_label(lines, "Total de los Servicios:"),
            "descuentos": _extract_amount_after_label(lines, "Total Descuento:"),
            "copagos": _extract_amount_after_label(lines, "Copagos - Pagos:"),
            "valor_total_factura": _extract_amount_after_label(lines, "Total de la Factura:"),
            "valor_en_letras": _extract_valor_en_letras(compact_text),
        },
        "observaciones": [],
    }


def _extract_provider_address(lines: list[str]) -> str:
    provider_name = _extract_provider_name(lines)
    if not provider_name:
        return ""
    for index, line in enumerate(lines):
        if _clean_text(line) != provider_name:
            continue
        for probe in range(index + 1, min(len(lines), index + 4)):
            candidate = _clean_text(lines[probe])
            normalized_candidate = _normalize_ascii(candidate)
            if not candidate:
                continue
            if normalized_candidate.startswith("factura electronica de venta"):
                continue
            if normalized_candidate.startswith("prefijo"):
                break
            return candidate
    return ""


def _pick_service_list(parsed_items: list[dict[str, Any]], fallback_items: Any) -> list[dict[str, Any]]:
    return parsed_items if parsed_items else list(fallback_items or [])


def _pick_section(*values: Any) -> str:
    return _coalesce(*values)


def build_factura_structured(
    *,
    raw_text: str,
    current_structured: dict[str, Any] | None,
    patient_name: str = "",
    patient_id: str = "",
    case_number: str = "",
) -> tuple[FacturaStructured, dict[str, Any]]:
    parsed = parse_factura_text(raw_text)
    current_model = FacturaStructured.model_validate(current_structured or {"dt": "factura"})

    servicios_parsed = parsed["servicios_procedimientos"]
    servicios_current = current_model.servicios_procedimientos

    normalized_payload = {
        "document_type": "factura",
        "patient_name": _pick_section(patient_name, parsed["nombre_paciente"], current_model.patient_name),
        "proveedor": {
            "nombre_institucion": _pick_section(
                parsed["proveedor"].get("nombre_institucion"),
                current_model.proveedor.nombre_institucion,
            ),
            "nit": _pick_section(parsed["proveedor"].get("nit"), current_model.proveedor.nit),
            "direccion": _pick_section(parsed["proveedor"].get("direccion"), current_model.proveedor.direccion),
            "ciudad_departamento": _pick_section(
                parsed["proveedor"].get("ciudad_departamento"),
                current_model.proveedor.ciudad_departamento,
            ),
        },
        "datos_factura": {
            "numero_factura": _pick_section(
                parsed["informacion_factura"].get("numero_factura"),
                current_model.datos_factura.numero_factura,
            ),
            "prefijo": _pick_section(parsed["informacion_factura"].get("prefijo"), current_model.datos_factura.prefijo),
            "fecha_emision": _pick_section(
                parsed["informacion_factura"].get("fecha_emision"),
                current_model.datos_factura.fecha_emision,
            ),
            "numero_caso": _pick_section(
                case_number,
                parsed["informacion_factura"].get("numero_caso"),
                current_model.datos_factura.numero_caso,
            ),
            "fecha_vencimiento": _pick_section(
                parsed["informacion_factura"].get("fecha_vencimiento"),
                current_model.datos_factura.fecha_vencimiento,
            ),
        },
        "pagador": {
            "aseguradora_eps": _pick_section(parsed["pagador"].get("aseguradora_eps"), current_model.pagador.aseguradora_eps),
            "nit_pagador": _pick_section(parsed["pagador"].get("nit_pagador"), current_model.pagador.nit_pagador),
            "tipo_convenio": _pick_section(parsed["pagador"].get("tipo_convenio"), current_model.pagador.tipo_convenio),
            "numero_autorizacion": _pick_section(
                parsed["pagador"].get("numero_autorizacion"),
                current_model.pagador.numero_autorizacion,
            ),
        },
        "paciente": {
            "nombre_completo": _pick_section(
                patient_name,
                parsed["informacion_paciente"].get("nombre_completo"),
                current_model.paciente.nombre_completo,
            ),
            "numero_identificacion": _pick_section(
                patient_id,
                parsed["informacion_paciente"].get("numero_identificacion"),
                current_model.paciente.numero_identificacion,
            ),
            "fecha_ingreso": _pick_section(
                parsed["informacion_paciente"].get("fecha_ingreso"),
                current_model.paciente.fecha_ingreso,
            ),
            "fecha_egreso": _pick_section(
                parsed["informacion_paciente"].get("fecha_egreso"),
                current_model.paciente.fecha_egreso,
            ),
            "estancia_hospitalaria": _pick_section(
                parsed["informacion_paciente"].get("estancia_hospitalaria"),
                current_model.paciente.estancia_hospitalaria,
            ),
        },
        "servicios_procedimientos": {
            "procedimientos_quirurgicos": _pick_service_list(
                servicios_parsed["procedimientos_quirurgicos"],
                servicios_current.procedimientos_quirurgicos,
            ),
            "examenes_laboratorio": _pick_service_list(
                servicios_parsed["examenes_laboratorio"],
                servicios_current.examenes_laboratorio,
            ),
            "imagenologia": _pick_service_list(
                servicios_parsed["imagenologia"],
                servicios_current.imagenologia,
            ),
            "hospitalizacion": _pick_service_list(
                servicios_parsed["hospitalizacion"],
                servicios_current.hospitalizacion,
            ),
            "honorarios_medicos": _pick_service_list(
                servicios_parsed["honorarios_medicos"],
                servicios_current.honorarios_medicos,
            ),
            "medicamentos": _pick_service_list(servicios_parsed["medicamentos"], servicios_current.medicamentos),
            "otros_servicios": _pick_service_list(
                servicios_parsed["otros_servicios"],
                getattr(servicios_current, "otros_servicios", []),
            ),
        },
        "resumen_financiero": {
            "total_servicios": _pick_section(
                parsed["analisis_financiero"].get("total_servicios"),
                current_model.resumen_financiero.total_servicios,
            ),
            "descuentos": _pick_section(
                parsed["analisis_financiero"].get("descuentos"),
                current_model.resumen_financiero.descuentos,
            ),
            "copagos": _pick_section(
                parsed["analisis_financiero"].get("copagos"),
                current_model.resumen_financiero.copagos,
            ),
            "valor_total_factura": _pick_section(
                parsed["analisis_financiero"].get("valor_total_factura"),
                current_model.resumen_financiero.valor_total_factura,
            ),
            "valor_en_letras": _pick_section(
                parsed["analisis_financiero"].get("valor_en_letras"),
                current_model.resumen_financiero.valor_en_letras,
            ),
            "observaciones_importantes": _pick_section(
                parsed["observaciones"][0] if parsed["observaciones"] else "",
                current_model.resumen_financiero.observaciones_importantes,
            ),
        },
    }

    normalized_model = FacturaStructured.model_validate(normalized_payload)
    factura_json = {
        "nombre_paciente": normalized_model.patient_name,
        "proveedor": normalized_model.proveedor.model_dump(exclude_none=True),
        "informacion_factura": normalized_model.datos_factura.model_dump(exclude_none=True),
        "pagador": normalized_model.pagador.model_dump(exclude_none=True),
        "informacion_paciente": normalized_model.paciente.model_dump(exclude_none=True),
        "servicios_procedimientos": normalized_model.servicios_procedimientos.model_dump(exclude_none=True),
        "analisis_financiero": normalized_model.resumen_financiero.model_dump(
            exclude_none=True,
            exclude={"observaciones_importantes"},
        ),
        "observaciones": (
            [normalized_model.resumen_financiero.observaciones_importantes]
            if normalized_model.resumen_financiero.observaciones_importantes
            else []
        ),
    }
    return normalized_model, factura_json
