"""Unit tests for extractors module."""

import pytest

from flat_fhir.extractors import (
    _get_coding, _get_text, _common_fields, _extract_observation_value,
    _is_vital_sign, _is_lab, _make_column_name,
    extract_observation, extract_patient, extract_medication_request,
    extract_condition, extract_allergy, extract_procedure,
    extract_imaging, extract_immunization, extract_encounter,
    extract_diagnostic_report,
)


# ── _get_coding ──

def test_get_coding_none():
    assert _get_coding(None) == ("", "", "")


def test_get_coding_empty():
    assert _get_coding({}) == ("", "", "")


def test_get_coding_text_only():
    assert _get_coding({"text": "Hello"}) == ("", "", "Hello")


def test_get_coding_with_coding():
    cc = {"coding": [{"system": "http://loinc.org", "code": "1234", "display": "Test"}]}
    assert _get_coding(cc) == ("http://loinc.org", "1234", "Test")


def test_get_coding_minimal_coding():
    cc = {"coding": [{"code": "X"}]}
    assert _get_coding(cc) == ("", "X", "")


# ── _get_text ──

def test_get_text_none():
    assert _get_text(None) == ""


def test_get_text_with_text():
    assert _get_text({"text": "Hello"}) == "Hello"


def test_get_text_fallback_to_display():
    assert _get_text({"coding": [{"display": "DisplayText"}]}) == "DisplayText"


def test_get_text_fallback_to_code():
    assert _get_text({"coding": [{"code": "ABC"}]}) == "ABC"


# ── _common_fields ──

def test_common_fields_basic():
    resource = {
        "effectiveDateTime": "2024-01-01",
        "encounter": {"reference": "Encounter/123"},
        "performer": [{"display": "Dr. Smith"}],
        "id": "obs-1",
        "note": [{"text": "Note 1"}, {"text": "Note 2"}],
    }
    result = _common_fields(resource)
    assert result["timestamp"] == "2024-01-01"
    assert result["encounter_id"] == "123"
    assert result["performer"] == "Dr. Smith"
    assert result["source_resource_id"] == "obs-1"
    assert result["notes"] == "Note 1 Note 2"


def test_common_fields_effective_period():
    resource = {"effectivePeriod": {"start": "2024-06-01"}}
    result = _common_fields(resource)
    assert result["timestamp"] == "2024-06-01"


def test_common_fields_issued_fallback():
    resource = {"issued": "2024-03-15"}
    result = _common_fields(resource)
    assert result["timestamp"] == "2024-03-15"


def test_common_fields_recorded_date_fallback():
    resource = {"recordedDate": "2024-07-20"}
    result = _common_fields(resource)
    assert result["timestamp"] == "2024-07-20"


def test_common_fields_performer_reference():
    resource = {"performer": [{"reference": "Practitioner/456"}]}
    result = _common_fields(resource)
    assert result["performer"] == "456"


def test_common_fields_no_encounter():
    resource = {}
    result = _common_fields(resource)
    assert result["encounter_id"] == ""
    assert result["performer"] == ""
    assert result["notes"] == ""


# ── _extract_observation_value ──

def test_value_quantity():
    obs = {"valueQuantity": {"value": 120, "unit": "mmHg"}}
    val, unit = _extract_observation_value(obs)
    assert val == 120
    assert unit == "mmHg"


def test_value_codeable_concept():
    obs = {"valueCodeableConcept": {"text": "Positive"}}
    val, unit = _extract_observation_value(obs)
    assert val == "Positive"


def test_value_string():
    obs = {"valueString": "Normal"}
    val, unit = _extract_observation_value(obs)
    assert val == "Normal"


def test_value_boolean():
    obs = {"valueBoolean": True}
    val, unit = _extract_observation_value(obs)
    assert val is True


def test_value_integer():
    obs = {"valueInteger": 42}
    val, unit = _extract_observation_value(obs)
    assert val == 42


def test_value_missing():
    val, unit = _extract_observation_value({})
    assert val is None


# ── _is_vital_sign / _is_lab ──

def test_is_vital_sign():
    obs = {"category": [{"coding": [{"code": "vital-signs"}]}]}
    assert _is_vital_sign(obs) is True
    assert _is_lab(obs) is False


def test_is_lab():
    obs = {"category": [{"coding": [{"code": "laboratory"}]}]}
    assert _is_lab(obs) is True
    assert _is_vital_sign(obs) is False


def test_neither_vital_nor_lab():
    obs = {"category": [{"coding": [{"code": "social-history"}]}]}
    assert _is_vital_sign(obs) is False
    assert _is_lab(obs) is False


def test_no_category():
    assert _is_vital_sign({}) is False
    assert _is_lab({}) is False


# ── _make_column_name ──

def test_make_column_name():
    assert _make_column_name("Heart Rate") == "heart_rate"
    assert _make_column_name("Body Weight (kg)") == "body_weight_kg"
    assert _make_column_name("") == "unknown"
    assert _make_column_name("  A-B/C  ") == "a_b_c"


# ── extract_observation ──

def test_extract_bp_panel():
    obs = {
        "resourceType": "Observation",
        "id": "bp-1",
        "code": {"coding": [{"system": "http://loinc.org", "code": "85354-9", "display": "Blood pressure panel"}]},
        "category": [{"coding": [{"code": "vital-signs"}]}],
        "effectiveDateTime": "2024-01-01",
        "component": [
            {
                "code": {"coding": [{"system": "http://loinc.org", "code": "8480-6", "display": "Systolic"}]},
                "valueQuantity": {"value": 120, "unit": "mmHg"},
            },
            {
                "code": {"coding": [{"system": "http://loinc.org", "code": "8462-4", "display": "Diastolic"}]},
                "valueQuantity": {"value": 80, "unit": "mmHg"},
            },
        ],
    }
    results = extract_observation(obs)
    assert len(results) == 2
    assert all(sheet == "vitals" for sheet, _ in results)


def test_extract_bp_panel_synthea_code():
    """BP panel with Synthea code 55284-4."""
    obs = {
        "resourceType": "Observation",
        "id": "bp-2",
        "code": {"coding": [{"system": "http://loinc.org", "code": "55284-4", "display": "BP"}]},
        "component": [
            {
                "code": {"coding": [{"system": "http://loinc.org", "code": "8480-6", "display": "Systolic"}]},
                "valueQuantity": {"value": 130, "unit": "mmHg"},
            },
        ],
    }
    results = extract_observation(obs)
    assert len(results) == 1
    assert results[0][0] == "vitals"


def test_extract_simple_vital():
    obs = {
        "resourceType": "Observation",
        "id": "hr-1",
        "code": {"coding": [{"system": "http://loinc.org", "code": "8867-4", "display": "Heart rate"}]},
        "category": [{"coding": [{"code": "vital-signs"}]}],
        "effectiveDateTime": "2024-01-01",
        "valueQuantity": {"value": 72, "unit": "/min"},
    }
    results = extract_observation(obs)
    assert len(results) == 1
    assert results[0][0] == "vitals"


def test_extract_lab_observation():
    obs = {
        "resourceType": "Observation",
        "id": "lab-1",
        "code": {"coding": [{"system": "http://loinc.org", "code": "4548-4", "display": "HbA1c"}]},
        "category": [{"coding": [{"code": "laboratory"}]}],
        "effectiveDateTime": "2024-01-01",
        "valueQuantity": {"value": 6.5, "unit": "%"},
    }
    results = extract_observation(obs)
    assert len(results) == 1
    assert results[0][0] == "labs"


def test_extract_observation_no_value():
    obs = {
        "resourceType": "Observation",
        "id": "no-val",
        "code": {"coding": [{"system": "http://loinc.org", "code": "1234", "display": "Test"}]},
        "category": [{"coding": [{"code": "laboratory"}]}],
    }
    results = extract_observation(obs)
    assert len(results) == 0


def test_extract_observation_no_category():
    """Observation without category — should use registry or default to labs."""
    obs = {
        "resourceType": "Observation",
        "id": "no-cat",
        "code": {"coding": [{"system": "http://loinc.org", "code": "9999-9", "display": "Unknown Test"}]},
        "valueQuantity": {"value": 42},
    }
    results = extract_observation(obs)
    assert len(results) == 1


def test_extract_generic_multi_component():
    """Non-BP observation with components."""
    obs = {
        "resourceType": "Observation",
        "id": "multi-1",
        "code": {"coding": [{"system": "http://loinc.org", "code": "9999-0", "display": "Panel"}]},
        "category": [{"coding": [{"code": "laboratory"}]}],
        "component": [
            {
                "code": {"coding": [{"system": "http://loinc.org", "code": "1111-1", "display": "SubTest A"}]},
                "valueQuantity": {"value": 10},
            },
            {
                "code": {"coding": [{"system": "http://loinc.org", "code": "2222-2", "display": "SubTest B"}]},
                "valueQuantity": {"value": 20},
            },
        ],
        "valueQuantity": {"value": 15},
    }
    results = extract_observation(obs)
    # 2 components + 1 top-level
    assert len(results) == 3


def test_extract_multi_component_with_known_codes():
    """Non-BP observation with components that have known registry codes."""
    obs = {
        "resourceType": "Observation",
        "id": "multi-known",
        "code": {"coding": [{"system": "http://loinc.org", "code": "9999-0", "display": "Panel"}]},
        "category": [{"coding": [{"code": "laboratory"}]}],
        "component": [
            {
                "code": {"coding": [{"system": "http://loinc.org", "code": "4548-4", "display": "HbA1c"}]},
                "valueQuantity": {"value": 6.5, "unit": "%"},
            },
        ],
        "valueQuantity": {"value": 6.5},
    }
    results = extract_observation(obs)
    # Component with known code should use mapping's column_name and sheet
    assert len(results) >= 1
    # The component with code 4548-4 should use the registry mapping
    component_results = [r for r in results if "hba1c" in str(r[1]).lower() or r[0] == "labs"]
    assert len(component_results) >= 1


# ── extract_patient ──

def test_extract_patient_full():
    patient = {
        "resourceType": "Patient",
        "id": "pat-1",
        "name": [{"family": "Doe", "given": ["John", "A"]}],
        "birthDate": "1990-01-01",
        "gender": "male",
        "telecom": [
            {"system": "phone", "value": "555-1234"},
            {"system": "email", "value": "john@example.com"},
        ],
        "address": [{
            "line": ["123 Main St"],
            "city": "Springfield",
            "state": "IL",
            "postalCode": "62701",
        }],
    }
    row = extract_patient(patient)
    assert row["family_name"] == "Doe"
    assert row["given_name"] == "John A"
    assert row["phone"] == "555-1234"
    assert row["email"] == "john@example.com"
    assert "123 Main St" in row["address"]
    assert "Springfield" in row["address"]


def test_extract_patient_minimal():
    patient = {"resourceType": "Patient", "id": "pat-2"}
    row = extract_patient(patient)
    assert row["family_name"] == ""
    assert row["given_name"] == ""


def test_extract_patient_empty_name_list():
    patient = {"resourceType": "Patient", "id": "pat-3", "name": []}
    row = extract_patient(patient)
    assert row["family_name"] == ""


# ── extract_medication_request ──

def test_extract_medication_request():
    med = {
        "resourceType": "MedicationRequest",
        "id": "med-1",
        "status": "active",
        "medicationCodeableConcept": {
            "coding": [{"system": "http://www.nlm.nih.gov/research/umls/rxnorm", "code": "860975", "display": "Metformin"}],
            "text": "Metformin",
        },
        "authoredOn": "2024-01-01",
        "dosageInstruction": [{
            "text": "Once daily",
            "doseAndRate": [{"doseQuantity": {"value": 500, "unit": "mg"}}],
            "route": {"text": "Oral"},
        }],
        "requester": {"display": "Dr. Smith"},
    }
    row = extract_medication_request(med)
    assert row["medication_name"] == "Metformin"
    assert row["rxnorm_code"] == "860975"
    assert row["dose"] == 500
    assert row["prescriber"] == "Dr. Smith"
    assert row["frequency"] == "Once daily"
    assert row["route"] == "Oral"


def test_extract_medication_reference():
    """MedicationRequest with medicationReference instead of CodeableConcept."""
    med = {
        "resourceType": "MedicationRequest",
        "id": "med-2",
        "status": "active",
        "medicationReference": {"display": "Aspirin 81mg"},
        "authoredOn": "2024-02-01",
        "requester": {"reference": "Practitioner/doc-1"},
    }
    row = extract_medication_request(med)
    assert row["medication_name"] == "Aspirin 81mg"
    assert row["prescriber"] == "doc-1"


def test_extract_medication_timing_code():
    """MedicationRequest with timing.code instead of text frequency."""
    med = {
        "resourceType": "MedicationRequest",
        "id": "med-3",
        "status": "active",
        "medicationCodeableConcept": {"text": "TestMed"},
        "dosageInstruction": [{
            "timing": {"code": {"text": "BID"}},
        }],
    }
    row = extract_medication_request(med)
    assert row["frequency"] == "BID"


# ── extract_condition ──

def test_extract_condition():
    cond = {
        "resourceType": "Condition",
        "id": "cond-1",
        "code": {
            "coding": [
                {"system": "http://snomed.info/sct", "code": "44054006", "display": "Diabetes"},
                {"system": "http://hl7.org/fhir/sid/icd-10-cm", "code": "E11.9", "display": "Diabetes"},
            ],
        },
        "clinicalStatus": {"coding": [{"code": "active"}]},
        "verificationStatus": {"coding": [{"code": "confirmed"}]},
        "category": [{"text": "encounter-diagnosis"}],
        "severity": {"text": "moderate"},
        "onsetDateTime": "2020-01-01",
        "abatementDateTime": "2024-12-31",
        "recordedDate": "2020-01-15",
    }
    row = extract_condition(cond)
    assert row["condition_name"] == "Diabetes"
    assert row["snomed_code"] == "44054006"
    assert row["icd_code"] == "E11.9"
    assert row["clinical_status"] == "active"
    assert row["verification_status"] == "confirmed"
    assert row["category"] == "encounter-diagnosis"
    assert row["severity"] == "moderate"
    assert row["onset_date"] == "2020-01-01"
    assert row["abatement_date"] == "2024-12-31"


# ── extract_allergy ──

def test_extract_allergy():
    allergy = {
        "resourceType": "AllergyIntolerance",
        "id": "allergy-1",
        "code": {"text": "Penicillin"},
        "type": "allergy",
        "category": ["medication"],
        "criticality": "high",
        "clinicalStatus": {"coding": [{"code": "active"}]},
        "reaction": [{"manifestation": [{"text": "Rash"}], "severity": "severe"}],
        "recordedDate": "2020-05-01",
    }
    row = extract_allergy(allergy)
    assert row["substance"] == "Penicillin"
    assert row["reaction"] == "Rash"
    assert row["severity"] == "severe"
    assert row["criticality"] == "high"
    assert row["type"] == "allergy"
    assert row["category"] == "medication"
    assert row["clinical_status"] == "active"


def test_extract_allergy_no_reaction():
    allergy = {
        "resourceType": "AllergyIntolerance",
        "id": "allergy-2",
        "code": {"text": "Shellfish"},
    }
    row = extract_allergy(allergy)
    assert row["substance"] == "Shellfish"
    assert row["reaction"] == ""


# ── extract_procedure ──

def test_extract_procedure():
    proc = {
        "resourceType": "Procedure",
        "id": "proc-1",
        "status": "completed",
        "code": {"coding": [{"system": "http://snomed.info/sct", "code": "29303009", "display": "ECG"}]},
        "performedDateTime": "2024-01-15",
        "bodySite": [{"text": "Chest"}],
        "outcome": {"text": "Normal"},
    }
    row = extract_procedure(proc)
    assert row["procedure_name"] == "ECG"
    assert row["snomed_code"] == "29303009"
    assert row["body_site"] == "Chest"
    assert row["outcome"] == "Normal"
    assert row["status"] == "completed"


def test_extract_procedure_performed_period():
    proc = {
        "resourceType": "Procedure",
        "id": "proc-2",
        "status": "completed",
        "code": {"text": "Surgery"},
        "performedPeriod": {"start": "2024-03-01", "end": "2024-03-01"},
    }
    row = extract_procedure(proc)
    assert row["timestamp"] == "2024-03-01"


# ── extract_imaging ──

def test_extract_imaging():
    study = {
        "resourceType": "ImagingStudy",
        "id": "img-1",
        "started": "2024-02-01",
        "series": [{
            "modality": {"code": "CT"},
            "bodySite": {"display": "Chest"},
        }],
        "description": "Normal CT findings",
        "identifier": [{"value": "1.2.3.4.5"}],
    }
    row = extract_imaging(study)
    assert row["modality"] == "CT"
    assert row["body_site"] == "Chest"
    assert row["findings"] == "Normal CT findings"
    assert row["dicom_uid"] == "1.2.3.4.5"


def test_extract_imaging_minimal():
    study = {"resourceType": "ImagingStudy", "id": "img-2"}
    row = extract_imaging(study)
    assert row["modality"] == ""
    assert row["dicom_uid"] == ""


# ── extract_immunization ──

def test_extract_immunization():
    imm = {
        "resourceType": "Immunization",
        "id": "imm-1",
        "status": "completed",
        "vaccineCode": {
            "coding": [{"system": "http://hl7.org/fhir/sid/cvx", "code": "140", "display": "Influenza"}],
        },
        "occurrenceDateTime": "2024-10-01",
        "lotNumber": "LOT123",
        "site": {"text": "Left arm"},
        "doseQuantity": {"value": 0.5, "unit": "mL"},
    }
    row = extract_immunization(imm)
    assert row["vaccine_name"] == "Influenza"
    assert row["cvx_code"] == "140"
    assert row["lot_number"] == "LOT123"
    assert row["site"] == "Left arm"
    assert row["dose"] == 0.5


def test_extract_immunization_minimal():
    imm = {"resourceType": "Immunization", "id": "imm-2", "status": "completed", "vaccineCode": {"text": "COVID"}}
    row = extract_immunization(imm)
    assert row["vaccine_name"] == "COVID"
    assert row["cvx_code"] == ""


# ── extract_encounter ──

def test_extract_encounter():
    enc = {
        "resourceType": "Encounter",
        "id": "enc-1",
        "status": "finished",
        "class": {"code": "AMB", "display": "ambulatory"},
        "type": [{"text": "Office Visit"}],
        "period": {"start": "2024-01-01T09:00:00Z", "end": "2024-01-01T10:00:00Z"},
        "participant": [{"individual": {"display": "Dr. Jones"}}],
        "serviceProvider": {"display": "Springfield Clinic"},
        "reasonCode": [{"text": "Annual checkup"}, {"text": "Follow-up"}],
        "hospitalization": {"dischargeDisposition": {"text": "Home"}},
    }
    row = extract_encounter(enc)
    assert row["encounter_id"] == "enc-1"
    assert row["class"] == "ambulatory"
    assert row["type"] == "Office Visit"
    assert row["facility"] == "Springfield Clinic"
    assert row["performer"] == "Dr. Jones"
    assert "Annual checkup" in row["reason"]
    assert "Follow-up" in row["reason"]
    assert row["discharge_disposition"] == "Home"
    assert row["period_start"] == "2024-01-01T09:00:00Z"
    assert row["period_end"] == "2024-01-01T10:00:00Z"


def test_extract_encounter_minimal():
    enc = {"resourceType": "Encounter", "id": "enc-2", "status": "finished"}
    row = extract_encounter(enc)
    assert row["encounter_id"] == "enc-2"
    assert row["class"] == ""
    assert row["type"] == ""


def test_extract_encounter_participant_reference():
    enc = {
        "resourceType": "Encounter",
        "id": "enc-3",
        "status": "finished",
        "participant": [{"individual": {"reference": "Practitioner/doc-1"}}],
    }
    row = extract_encounter(enc)
    assert row["performer"] == "doc-1"


# ── extract_diagnostic_report ──

def test_extract_diagnostic_report():
    report = {
        "resourceType": "DiagnosticReport",
        "id": "dr-1",
        "code": {"coding": [{"system": "http://loinc.org", "code": "58410-2", "display": "CBC"}]},
        "category": [{"text": "Hematology"}],
        "status": "final",
        "effectiveDateTime": "2024-01-01",
        "conclusion": "Normal",
        "issued": "2024-01-02",
        "result": [
            {"reference": "Observation/obs-1"},
            {"reference": "Observation/obs-2"},
        ],
    }
    row = extract_diagnostic_report(report)
    assert row["report_name"] == "CBC"
    assert row["loinc_code"] == "58410-2"
    assert row["category"] == "Hematology"
    assert row["conclusion"] == "Normal"
    assert "Observation/obs-1" in row["result_references"]
    assert "Observation/obs-2" in row["result_references"]


def test_extract_diagnostic_report_effective_period():
    report = {
        "resourceType": "DiagnosticReport",
        "id": "dr-2",
        "code": {"text": "X-Ray"},
        "status": "final",
        "effectivePeriod": {"start": "2024-06-01"},
    }
    row = extract_diagnostic_report(report)
    assert row["timestamp"] == "2024-06-01"
    assert row["report_name"] == "X-Ray"
