"""Extract flat rows from FHIR resources, one extractor per sheet type."""

from __future__ import annotations

from typing import Any

from flat_fhir.registry import _registry, LOINC, SNOMED, ICD10, RXNORM, CVX


def _get_coding(codeable_concept: dict | None) -> tuple[str, str, str]:
    """Return (system, code, display) from the first coding in a CodeableConcept."""
    if not codeable_concept:
        return ("", "", "")
    for coding in codeable_concept.get("coding", []):
        return (coding.get("system", ""), coding.get("code", ""),
                coding.get("display", ""))
    return ("", "", codeable_concept.get("text", ""))


def _get_text(codeable_concept: dict | None) -> str:
    if not codeable_concept:
        return ""
    text = codeable_concept.get("text", "")
    if text:
        return text
    system, code, display = _get_coding(codeable_concept)
    return display or code


def _common_fields(resource: dict, effective_key: str = "effectiveDateTime") -> dict:
    """Extract common columns shared across sheets."""
    ts = resource.get(effective_key, "")
    if not ts and "effectivePeriod" in resource:
        ts = resource["effectivePeriod"].get("start", "")
    if not ts:
        ts = resource.get("issued", "") or resource.get("recordedDate", "") or ""

    encounter_ref = resource.get("encounter", {}).get("reference", "")
    encounter_id = encounter_ref.split("/")[-1] if encounter_ref else ""

    performers = resource.get("performer", [])
    performer = ""
    if performers:
        performer = performers[0].get("display", "")
        if not performer:
            ref = performers[0].get("reference", "")
            performer = ref.split("/")[-1] if ref else ""

    notes = ""
    for note in resource.get("note", []):
        notes += note.get("text", "") + " "

    return {
        "timestamp": ts,
        "encounter_id": encounter_id,
        "performer": performer,
        "source_resource_id": resource.get("id", ""),
        "notes": notes.strip(),
    }


def _extract_observation_value(obs: dict) -> tuple[Any, str]:
    """Extract value and unit from an Observation."""
    if "valueQuantity" in obs:
        vq = obs["valueQuantity"]
        return (vq.get("value"), vq.get("unit", ""))
    if "valueCodeableConcept" in obs:
        return (_get_text(obs["valueCodeableConcept"]), "")
    if "valueString" in obs:
        return (obs["valueString"], "")
    if "valueBoolean" in obs:
        return (obs["valueBoolean"], "")
    if "valueInteger" in obs:
        return (obs["valueInteger"], "")
    return (None, "")


def _is_vital_sign(obs: dict) -> bool:
    for cat in obs.get("category", []):
        for coding in cat.get("coding", []):
            if coding.get("code") == "vital-signs":
                return True
    return False


def _is_lab(obs: dict) -> bool:
    for cat in obs.get("category", []):
        for coding in cat.get("coding", []):
            if coding.get("code") == "laboratory":
                return True
    return False


def extract_observation(obs: dict) -> list[tuple[str, dict]]:
    """Extract one or more (sheet, row) tuples from an Observation resource.

    Blood pressure panels produce two rows (systolic + diastolic) on the vitals sheet.
    Other observations produce one row on either vitals or labs depending on category.
    """
    results = []
    common = _common_fields(obs)
    system, code, display = _get_coding(obs.get("code"))

    # Blood pressure panel — extract components (85354-9 from spec, 55284-4 from Synthea)
    bp_codes = {"85354-9", "55284-4"}
    if code in bp_codes and obs.get("component"):
        for comp in obs["component"]:
            c_sys, c_code, c_display = _get_coding(comp.get("code"))
            mapping = _registry.lookup_code(c_sys, c_code)
            col_name = mapping.column_name if mapping else _make_column_name(c_display)
            value, _ = _extract_observation_value(comp)
            if value is not None:
                row = {**common, col_name: value}
                results.append(("vitals", row))
        return results

    # Generic multi-component observations (e.g., panels) — extract each component
    if obs.get("component") and code not in bp_codes:
        for comp in obs["component"]:
            c_sys, c_code, c_display = _get_coding(comp.get("code"))
            mapping = _registry.lookup_code(c_sys, c_code)
            sheet = "vitals" if _is_vital_sign(obs) else "labs"
            if mapping:
                col_name = mapping.column_name
                sheet = mapping.sheet
            else:
                col_name = _make_column_name(c_display or c_code)
            value, _ = _extract_observation_value(comp)
            if value is not None:
                row = {**common, col_name: value}
                results.append((sheet, row))
        # Also extract the top-level value if present
        value, _ = _extract_observation_value(obs)
        if value is not None:
            mapping = _registry.lookup_code(system, code)
            col_name = mapping.column_name if mapping else _make_column_name(display or code)
            sheet = mapping.sheet if mapping else ("vitals" if _is_vital_sign(obs) else "labs")
            row = {**common, col_name: value}
            results.append((sheet, row))
        if results:
            return results

    # Determine sheet
    if _is_vital_sign(obs):
        sheet = "vitals"
    elif _is_lab(obs):
        sheet = "labs"
    else:
        # Try registry
        mapping = _registry.lookup_code(system, code)
        sheet = mapping.sheet if mapping else "labs"

    mapping = _registry.lookup_code(system, code)
    col_name = mapping.column_name if mapping else _make_column_name(display or code)

    value, _ = _extract_observation_value(obs)
    if value is not None:
        row = {**common, col_name: value}
        results.append((sheet, row))

    return results


def extract_patient(patient: dict) -> dict:
    """Extract demographics from a Patient resource."""
    names = patient.get("name", [{}])
    name = names[0] if names else {}

    telecoms = patient.get("telecom", [])
    phone = ""
    email = ""
    for t in telecoms:
        if t.get("system") == "phone":
            phone = t.get("value", "")
        elif t.get("system") == "email":
            email = t.get("value", "")

    addresses = patient.get("address", [{}])
    addr = addresses[0] if addresses else {}
    address_parts = []
    for line in addr.get("line", []):
        address_parts.append(line)
    for part in ["city", "state", "postalCode", "country"]:
        if addr.get(part):
            address_parts.append(addr[part])
    address = ", ".join(address_parts)

    return {
        "source_resource_id": patient.get("id", ""),
        "family_name": name.get("family", ""),
        "given_name": " ".join(name.get("given", [])),
        "birth_date": patient.get("birthDate", ""),
        "gender": patient.get("gender", ""),
        "phone": phone,
        "email": email,
        "address": address,
    }


def extract_medication_request(med: dict) -> dict:
    """Extract a row from a MedicationRequest resource."""
    common = _common_fields(med, effective_key="authoredOn")
    common["timestamp"] = med.get("authoredOn", common["timestamp"])

    med_code = med.get("medicationCodeableConcept", {})
    system, code, display = _get_coding(med_code)

    # Try medicationReference
    if not display:
        display = med.get("medicationReference", {}).get("display", "")

    dosage = {}
    dosage_list = med.get("dosageInstruction", [])
    if dosage_list:
        d = dosage_list[0]
        dose_qty = d.get("doseAndRate", [{}])[0].get("doseQuantity", {}) if d.get("doseAndRate") else {}
        dosage = {
            "dose": dose_qty.get("value", ""),
            "dose_unit": dose_qty.get("unit", ""),
            "frequency": d.get("text", ""),
            "route": _get_text(d.get("route")),
        }
        if not dosage["frequency"] and d.get("timing", {}).get("code"):
            dosage["frequency"] = _get_text(d["timing"]["code"])

    requester = med.get("requester", {})
    prescriber = requester.get("display", "")
    if not prescriber:
        ref = requester.get("reference", "")
        prescriber = ref.split("/")[-1] if ref else ""

    return {
        **common,
        "medication_name": display,
        "rxnorm_code": code if system and "rxnorm" in system.lower() else "",
        "status": med.get("status", ""),
        "prescriber": prescriber,
        **dosage,
    }


def extract_condition(cond: dict) -> dict:
    """Extract a row from a Condition resource."""
    common = _common_fields(cond, effective_key="recordedDate")
    common["timestamp"] = cond.get("recordedDate", "") or cond.get("onsetDateTime", "") or common["timestamp"]

    system, code, display = _get_coding(cond.get("code"))

    icd_code = ""
    snomed_code = ""
    for coding in cond.get("code", {}).get("coding", []):
        s = coding.get("system", "")
        c = coding.get("code", "")
        if "icd" in s.lower():
            icd_code = c
        if "snomed" in s.lower():
            snomed_code = c

    clinical_status = _get_text(cond.get("clinicalStatus"))
    verification_status = _get_text(cond.get("verificationStatus"))

    category = ""
    if cond.get("category"):
        category = _get_text(cond["category"][0])

    severity = _get_text(cond.get("severity"))

    return {
        **common,
        "condition_name": display or _get_text(cond.get("code")),
        "icd_code": icd_code,
        "snomed_code": snomed_code,
        "clinical_status": clinical_status,
        "verification_status": verification_status,
        "category": category,
        "severity": severity,
        "onset_date": cond.get("onsetDateTime", ""),
        "abatement_date": cond.get("abatementDateTime", ""),
    }


def extract_allergy(allergy: dict) -> dict:
    """Extract a row from an AllergyIntolerance resource."""
    common = _common_fields(allergy, effective_key="recordedDate")
    common["timestamp"] = allergy.get("recordedDate", "") or common["timestamp"]

    substance = _get_text(allergy.get("code"))

    reactions = allergy.get("reaction", [])
    reaction = ""
    severity = ""
    if reactions:
        r = reactions[0]
        reaction = _get_text(r.get("manifestation", [{}])[0]) if r.get("manifestation") else ""
        severity = r.get("severity", "")

    allergy_type = allergy.get("type", "")

    categories = allergy.get("category", [])
    category = categories[0] if categories else ""

    clinical_status = _get_text(allergy.get("clinicalStatus"))

    return {
        **common,
        "substance": substance,
        "reaction": reaction,
        "severity": severity,
        "criticality": allergy.get("criticality", ""),
        "type": allergy_type,
        "category": category,
        "clinical_status": clinical_status,
    }


def extract_procedure(proc: dict) -> dict:
    """Extract a row from a Procedure resource."""
    common = _common_fields(proc, effective_key="performedDateTime")
    if "performedPeriod" in proc:
        common["timestamp"] = proc["performedPeriod"].get("start", common["timestamp"])
    elif "performedDateTime" in proc:
        common["timestamp"] = proc["performedDateTime"]

    system, code, display = _get_coding(proc.get("code"))

    body_site = ""
    if proc.get("bodySite"):
        body_site = _get_text(proc["bodySite"][0])

    outcome = _get_text(proc.get("outcome"))

    return {
        **common,
        "procedure_name": display or _get_text(proc.get("code")),
        "snomed_code": code if system and "snomed" in system.lower() else "",
        "body_site": body_site,
        "outcome": outcome,
        "status": proc.get("status", ""),
    }


def extract_imaging(study: dict) -> dict:
    """Extract a row from an ImagingStudy resource."""
    common = _common_fields(study, effective_key="started")
    common["timestamp"] = study.get("started", common["timestamp"])

    modality = ""
    body_site = ""
    series = study.get("series", [])
    if series:
        s = series[0]
        mod = s.get("modality", {})
        modality = mod.get("code", "") or mod.get("display", "")
        if s.get("bodySite"):
            body_site = s["bodySite"].get("display", "") or s["bodySite"].get("code", "")

    findings = ""
    if study.get("description"):
        findings = study["description"]

    return {
        **common,
        "modality": modality,
        "body_site": body_site,
        "dicom_uid": study.get("identifier", [{}])[0].get("value", "") if study.get("identifier") else "",
        "findings": findings,
    }


def extract_immunization(imm: dict) -> dict:
    """Extract a row from an Immunization resource."""
    common = _common_fields(imm, effective_key="occurrenceDateTime")
    common["timestamp"] = imm.get("occurrenceDateTime", "") or common["timestamp"]

    system, code, display = _get_coding(imm.get("vaccineCode"))

    cvx_code = ""
    for coding in imm.get("vaccineCode", {}).get("coding", []):
        if "cvx" in coding.get("system", "").lower():
            cvx_code = coding.get("code", "")

    dose_qty = imm.get("doseQuantity", {})
    site = _get_text(imm.get("site"))

    return {
        **common,
        "vaccine_name": display or _get_text(imm.get("vaccineCode")),
        "cvx_code": cvx_code or (code if system and "cvx" in system.lower() else ""),
        "dose": dose_qty.get("value", ""),
        "dose_unit": dose_qty.get("unit", ""),
        "site": site,
        "lot_number": imm.get("lotNumber", ""),
        "status": imm.get("status", ""),
    }


def extract_encounter(enc: dict) -> dict:
    """Extract a row from an Encounter resource."""
    period = enc.get("period", {})
    timestamp = period.get("start", "")

    participants = enc.get("participant", [])
    performer = ""
    if participants:
        ind = participants[0].get("individual", {})
        performer = ind.get("display", "")
        if not performer:
            ref = ind.get("reference", "")
            performer = ref.split("/")[-1] if ref else ""

    enc_type = ""
    if enc.get("type"):
        enc_type = _get_text(enc["type"][0])

    facility = ""
    if enc.get("serviceProvider"):
        facility = enc["serviceProvider"].get("display", "")

    reason = ""
    for r in enc.get("reasonCode", []):
        reason += _get_text(r) + "; "
    reason = reason.rstrip("; ")

    discharge = ""
    hosp = enc.get("hospitalization", {})
    if hosp.get("dischargeDisposition"):
        discharge = _get_text(hosp["dischargeDisposition"])

    notes = ""
    for note in enc.get("note", []):
        notes += note.get("text", "") + " "

    # Encounter class (AMB, EMER, IMP, etc.)
    enc_class = enc.get("class", {})
    enc_class_code = enc_class.get("code", "") if isinstance(enc_class, dict) else ""
    enc_class_display = enc_class.get("display", "") if isinstance(enc_class, dict) else ""

    return {
        "timestamp": timestamp,
        "encounter_id": enc.get("id", ""),
        "performer": performer,
        "source_resource_id": enc.get("id", ""),
        "notes": notes.strip(),
        "class": enc_class_display or enc_class_code,
        "type": enc_type,
        "facility": facility,
        "reason": reason,
        "discharge_disposition": discharge,
        "status": enc.get("status", ""),
        "period_start": period.get("start", ""),
        "period_end": period.get("end", ""),
    }


def extract_diagnostic_report(report: dict) -> dict:
    """Extract a row from a DiagnosticReport resource."""
    common = _common_fields(report, effective_key="effectiveDateTime")
    if "effectivePeriod" in report:
        common["timestamp"] = report["effectivePeriod"].get("start", common["timestamp"])

    system, code, display = _get_coding(report.get("code"))

    category = ""
    if report.get("category"):
        category = _get_text(report["category"][0])

    # Collect result references (these link to Observation resources)
    result_refs = []
    for ref in report.get("result", []):
        result_refs.append(ref.get("reference", ""))

    return {
        **common,
        "report_name": display or _get_text(report.get("code")),
        "loinc_code": code if system and "loinc" in system.lower() else "",
        "category": category,
        "status": report.get("status", ""),
        "conclusion": report.get("conclusion", ""),
        "issued": report.get("issued", ""),
        "result_references": "; ".join(r for r in result_refs if r),
    }


def _make_column_name(display: str) -> str:
    """Convert a display string to snake_case column name."""
    import re
    name = display.lower().strip()
    name = re.sub(r'[^a-z0-9]+', '_', name)
    name = name.strip('_')
    return name or "unknown"
