from __future__ import annotations

import re

from app.rda.domain.fhir_projection import (
    FhirLikeBundle,
    FhirLikeBundleEntry,
    FhirLikeComposition,
    FhirLikeEncounter,
    FhirLikeOrganization,
    FhirLikePatient,
    FhirLikePractitioner,
    FhirLikeReference,
    RdaFhirProjection,
)
from app.rda.domain.models import RdaArtifact, RdaArtifactType, RdaEmergencySummary, RdaPatientSummary


_NON_ALNUM_RE = re.compile(r"[^a-z0-9]+")


def build_rda_fhir_projection(artifact: RdaArtifact) -> RdaFhirProjection:
    if artifact.artifact_type == RdaArtifactType.PATIENT:
        payload = RdaPatientSummary(**artifact.payload)
        return _build_patient_projection(artifact, payload)

    payload = RdaEmergencySummary(**artifact.payload)
    return _build_emergency_projection(artifact, payload)


def _build_patient_projection(artifact: RdaArtifact, payload: RdaPatientSummary) -> RdaFhirProjection:
    patient_id = _resource_id(artifact.case_key, artifact.artifact_type, "patient")
    composition_id = _resource_id(artifact.case_key, artifact.artifact_type, "composition")
    bundle_id = _resource_id(artifact.case_key, artifact.artifact_type, "bundle")

    patient = FhirLikePatient(
        id=patient_id,
        name=_string_or_none(payload.patient.get("name")),
        identifier_type=_resolve_nested_string(payload.patient, "document", "type"),
        identifier_value=_resolve_nested_string(payload.patient, "document", "number"),
        birth_date=_string_or_none(payload.patient.get("birth_date")),
        sex=_string_or_none(payload.patient.get("sex")),
    )
    patient_reference = _reference("Patient", patient.id)
    organizations = _build_organizations(
        artifact.case_key,
        artifact.artifact_type,
        provider=payload.organization.get("provider") if isinstance(payload.organization, dict) else None,
        payer=payload.organization.get("payer") if isinstance(payload.organization, dict) else None,
    )
    organization_references = [_reference("Organization", item.id) for item in organizations]

    composition = FhirLikeComposition(
        id=composition_id,
        title="RDA Patient Summary",
        subject=patient_reference,
        organization_references=organization_references,
    )
    bundle = FhirLikeBundle(
        id=bundle_id,
        entries=_bundle_entries(
            patient_reference,
            _reference("Composition", composition.id),
            *organization_references,
        ),
    )

    projection_gaps = _projection_gaps_for_patient(payload)
    return RdaFhirProjection(
        artifact_type=artifact.artifact_type,
        bundle=bundle,
        composition=composition,
        patient=patient,
        organizations=organizations,
        projection_gaps=projection_gaps,
    )


def _build_emergency_projection(artifact: RdaArtifact, payload: RdaEmergencySummary) -> RdaFhirProjection:
    patient_id = _resource_id(artifact.case_key, artifact.artifact_type, "patient")
    encounter_id = _resource_id(artifact.case_key, artifact.artifact_type, "encounter")
    composition_id = _resource_id(artifact.case_key, artifact.artifact_type, "composition")
    bundle_id = _resource_id(artifact.case_key, artifact.artifact_type, "bundle")

    patient = FhirLikePatient(
        id=patient_id,
        name=_string_or_none(payload.patient.get("name")),
        identifier_type=_resolve_nested_string(payload.patient, "document", "type"),
        identifier_value=_resolve_nested_string(payload.patient, "document", "number"),
    )
    patient_reference = _reference("Patient", patient.id)

    encounter_provider = _resolve_nested_dict(payload.encounter, "provider")
    encounter_payer = _resolve_nested_dict(payload.encounter, "payer")
    organizations = _build_organizations(
        artifact.case_key,
        artifact.artifact_type,
        provider=encounter_provider,
        payer=encounter_payer,
    )
    provider_references = [_reference("Organization", item.id) for item in organizations if item.role == "provider"]
    payer_references = [_reference("Organization", item.id) for item in organizations if item.role == "payer"]

    practitioner_name = _string_or_none(payload.encounter.get("responsible_professional"))
    practitioner = (
        FhirLikePractitioner(
            id=_resource_id(artifact.case_key, artifact.artifact_type, "practitioner"),
            name=practitioner_name,
        )
        if practitioner_name
        else None
    )
    practitioner_reference = (
        _reference("Practitioner", practitioner.id) if practitioner is not None else None
    )

    encounter = FhirLikeEncounter(
        id=encounter_id,
        case_key=artifact.case_key,
        case_number=_string_or_none(payload.encounter.get("case_number")),
        admission_at=_string_or_none(payload.encounter.get("admission_at")),
        reason=_string_or_none(payload.encounter.get("reason")),
        triage=_string_or_none(payload.encounter.get("triage")),
        subject=patient_reference,
        provider_references=provider_references,
        payer_references=payer_references,
        practitioner_reference=practitioner_reference,
    )
    encounter_reference = _reference("Encounter", encounter.id)

    composition = FhirLikeComposition(
        id=composition_id,
        title="RDA Emergency Summary",
        subject=patient_reference,
        encounter=encounter_reference,
        organization_references=[*provider_references, *payer_references],
        practitioner_reference=practitioner_reference,
    )
    bundle = FhirLikeBundle(
        id=bundle_id,
        entries=_bundle_entries(
            patient_reference,
            _reference("Composition", composition.id),
            encounter_reference,
            *provider_references,
            *payer_references,
            *((practitioner_reference,) if practitioner_reference is not None else ()),
        ),
    )

    projection_gaps = _projection_gaps_for_emergency(payload, practitioner_present=practitioner is not None)
    return RdaFhirProjection(
        artifact_type=artifact.artifact_type,
        bundle=bundle,
        composition=composition,
        patient=patient,
        encounter=encounter,
        organizations=organizations,
        practitioner=practitioner,
        projection_gaps=projection_gaps,
    )


def _build_organizations(
    case_key: str,
    artifact_type: RdaArtifactType,
    *,
    provider: dict | None,
    payer: dict | None,
) -> list[FhirLikeOrganization]:
    organizations: list[FhirLikeOrganization] = []
    provider_name = _string_or_none((provider or {}).get("name"))
    provider_identifier = _string_or_none((provider or {}).get("nit"))
    if provider_name or provider_identifier:
        organizations.append(
            FhirLikeOrganization(
                id=_resource_id(case_key, artifact_type, "provider"),
                role="provider",
                name=provider_name,
                identifier_value=provider_identifier,
            )
        )

    payer_name = _string_or_none((payer or {}).get("name"))
    payer_identifier = _string_or_none((payer or {}).get("nit"))
    if payer_name or payer_identifier:
        organizations.append(
            FhirLikeOrganization(
                id=_resource_id(case_key, artifact_type, "payer"),
                role="payer",
                name=payer_name,
                identifier_value=payer_identifier,
            )
        )
    return organizations


def _projection_gaps_for_patient(payload: RdaPatientSummary) -> list[str]:
    gaps = ["document_reference:not_projected_in_v1"]
    if payload.clinical_background:
        gaps.append("clinical_background:no_explicit_resource_v1")
    if payload.diagnoses:
        gaps.append("diagnoses:no_condition_resource_v1")
    return gaps


def _projection_gaps_for_emergency(
    payload: RdaEmergencySummary,
    *,
    practitioner_present: bool,
) -> list[str]:
    gaps = ["document_reference:not_projected_in_v1"]
    clinical = payload.clinical if isinstance(payload.clinical, dict) else {}
    if clinical.get("diagnoses"):
        gaps.append("diagnoses:no_condition_resource_v1")
    if clinical.get("procedures"):
        gaps.append("procedures:no_procedure_resource_v1")
    if clinical.get("medications"):
        gaps.append("medications:no_medication_resource_v1")
    if clinical.get("diagnostic_support"):
        gaps.append("diagnostic_support:no_diagnostic_report_resource_v1")
    if not practitioner_present:
        gaps.append("practitioner:missing_source_data")
    return gaps


def _bundle_entries(*references: FhirLikeReference) -> list[FhirLikeBundleEntry]:
    return [
        FhirLikeBundleEntry(
            full_url=reference.reference,
            reference=reference,
        )
        for reference in references
    ]


def _reference(resource_label: str, resource_id: str) -> FhirLikeReference:
    return FhirLikeReference(reference=f"{resource_label}/{resource_id}")


def _resource_id(case_key: str, artifact_type: RdaArtifactType, suffix: str) -> str:
    normalized_case_key = _NON_ALNUM_RE.sub("-", str(case_key or "").strip().lower()).strip("-") or "case"
    normalized_suffix = _NON_ALNUM_RE.sub("-", str(suffix or "").strip().lower()).strip("-") or "resource"
    return f"{normalized_case_key}-{artifact_type.value}-{normalized_suffix}"


def _resolve_nested_string(data: dict, *path: str) -> str | None:
    nested = _resolve_nested_dict(data, *path[:-1])
    if not path:
        return None
    return _string_or_none(nested.get(path[-1]))


def _resolve_nested_dict(data: dict, *path: str) -> dict:
    current = data
    for key in path:
        value = current.get(key)
        if not isinstance(value, dict):
            return {}
        current = value
    return current


def _string_or_none(value: object) -> str | None:
    normalized = str(value).strip() if value is not None else ""
    return normalized or None
