"""Tests for the FastAPI surface in ``cyclone.api`` for the 270/271
eligibility endpoints (SP3 P4 T23 + T24).
"""
from __future__ import annotations

from datetime import date
from pathlib import Path

import pytest
from fastapi.testclient import TestClient

from cyclone.api import app
from cyclone.parsers.parse_270 import parse as parse_270_text

MINIMAL_271 = Path(__file__).parent / "fixtures" / "minimal_271.txt"


@pytest.fixture
def client() -> TestClient:
    return TestClient(app)


def _valid_request_body() -> dict:
    return {
        "subscriber": {
            "first_name": "JOHN",
            "last_name": "DOE",
            "member_id": "MEMBER123",
            "dob": "1980-01-01",
        },
        "provider": {
            "npi": "1234567890",
            "name": "PROVIDER NAME",
        },
        "payer": {
            "id": "SKCO0",
            "name": "PAYER NAME",
        },
        "service_type_code": "1",
    }


# --------------------------------------------------------------------------- #
# T23: POST /api/eligibility/request
# --------------------------------------------------------------------------- #


def test_eligibility_request_returns_raw_270_text(client: TestClient):
    """A minimal body returns the X12 270 text + the parsed structure."""
    body = _valid_request_body()
    resp = client.post("/api/eligibility/request", json=body)
    assert resp.status_code == 200, resp.text
    payload = resp.json()
    assert "raw_270_text" in payload
    assert "parsed" in payload
    raw = payload["raw_270_text"]
    assert isinstance(raw, str) and raw
    # Canonical X12 envelope markers.
    assert raw.startswith("ISA*")
    assert raw.rstrip("\n").endswith("~IEA*1*000000001~")
    # All the required segments are present.
    assert "HL*1**20*1~" in raw
    assert "NM1*PR*2*PAYER NAME" in raw
    assert "HL*2*1*21*1~" in raw
    assert "NM1*1P*2*PROVIDER NAME" in raw
    assert "NM1*IL*1*DOE*JOHN" in raw
    assert "EQ*1~" in raw
    # The parsed payload mirrors the input.
    parsed = payload["parsed"]
    assert parsed["subscriber"]["first_name"] == "JOHN"
    assert parsed["subscriber"]["last_name"] == "DOE"
    assert parsed["subscriber"]["member_id"] == "MEMBER123"
    assert parsed["inquiries"][0]["service_type_code"] == "1"


def test_eligibility_request_parsed_matches_serialized(client: TestClient):
    """The endpoint's ``parsed`` field round-trips through a server-side
    re-parse of ``raw_270_text``.
    """
    body = _valid_request_body()
    resp = client.post("/api/eligibility/request", json=body)
    assert resp.status_code == 200, resp.text
    payload = resp.json()
    raw = payload["raw_270_text"]
    re_parsed = parse_270_text(raw, input_file="round_trip.txt")
    # The re-parsed structure must equal the endpoint's parsed payload
    # for the fields the operator can observe.
    parsed = payload["parsed"]
    assert re_parsed.subscriber.first_name == parsed["subscriber"]["first_name"]
    assert re_parsed.subscriber.last_name == parsed["subscriber"]["last_name"]
    assert re_parsed.subscriber.member_id == parsed["subscriber"]["member_id"]
    assert re_parsed.information_source.name == parsed["information_source"]["name"]
    assert re_parsed.information_receiver.npi == parsed["information_receiver"]["npi"]
    assert re_parsed.inquiries[0].service_type_code == parsed["inquiries"][0]["service_type_code"]


def test_eligibility_request_missing_service_type_returns_400(client: TestClient):
    """Missing ``service_type_code`` returns 400, never 500."""
    body = _valid_request_body()
    del body["service_type_code"]
    resp = client.post("/api/eligibility/request", json=body)
    assert resp.status_code == 400, resp.text
    detail = resp.json()["detail"]
    assert "service_type_code" in str(detail)


def test_eligibility_request_missing_subscriber_member_id_returns_400(client: TestClient):
    """Missing ``subscriber.member_id`` returns 400."""
    body = _valid_request_body()
    del body["subscriber"]["member_id"]
    resp = client.post("/api/eligibility/request", json=body)
    assert resp.status_code == 400, resp.text


# --------------------------------------------------------------------------- #
# T24: POST /api/eligibility/parse-271
# --------------------------------------------------------------------------- #


def test_parse_271_endpoint_happy_path(client: TestClient):
    """Uploading the minimal 271 returns 200 + a non-empty
    ``coverage_benefits`` array.
    """
    text = MINIMAL_271.read_text()
    resp = client.post(
        "/api/eligibility/parse-271",
        files={"file": ("minimal_271.txt", text, "text/plain")},
    )
    assert resp.status_code == 200, resp.text
    body = resp.json()
    assert "coverage_benefits" in body
    assert "subscriber" in body
    assert "summary" in body
    assert isinstance(body["coverage_benefits"], list)
    assert len(body["coverage_benefits"]) == 2
    # The first EB resolves to "Medical Care".
    cb0 = body["coverage_benefits"][0]
    assert cb0["service_type_code"] == "1"
    assert cb0["service_type_description"] == "Medical Care"
    assert cb0["in_plan_network"] is True
    # The second EB is pharmacy.
    cb1 = body["coverage_benefits"][1]
    assert cb1["service_type_code"] == "88"
    assert cb1["service_type_description"] == "Pharmacy"
    assert cb1["in_plan_network"] is False
    # Subscriber extracted.
    assert body["subscriber"]["first_name"] == "JOHN"
    assert body["subscriber"]["last_name"] == "DOE"
    assert body["subscriber"]["member_id"] == "MEMBER123"


def test_parse_271_endpoint_invalid_edi_raises_400(client: TestClient):
    """Garbage input must surface as 400, never 500."""
    resp = client.post(
        "/api/eligibility/parse-271",
        files={"file": ("garbage.txt", "not edi", "text/plain")},
    )
    assert resp.status_code == 400, resp.text
    body = resp.json()
    assert "error" in body


def test_parse_271_endpoint_empty_file_returns_400(client: TestClient):
    """An empty upload returns 400 (matches the 999 / 835 contract)."""
    resp = client.post(
        "/api/eligibility/parse-271",
        files={"file": ("empty.txt", "", "text/plain")},
    )
    assert resp.status_code == 400, resp.text
