"""Tests for the 270 (Eligibility Benefit Inquiry) serializer.

SP3 Phase 4 (T22 + T25): build a ``ParseResult270``, serialize to
X12, then re-parse and assert the round-trip is stable. The cross-
parser round-trip test (T25) lives at the bottom of this file.
"""
from __future__ import annotations

from datetime import date

import pytest

from cyclone.parsers.models import BatchSummary, Envelope
from cyclone.parsers.models_270 import (
    EligibilityBenefitInquiry,
    InformationReceiver270,
    InformationSource270,
    ParseResult270,
    Subscriber270,
)
from cyclone.parsers.parse_270 import parse
from cyclone.parsers.serialize_270 import serialize_270


def _build_result(
    service_type_code: str = "1",
    with_patient: bool = False,
) -> ParseResult270:
    patient = None
    if with_patient:
        from cyclone.parsers.models_270 import Patient270
        patient = Patient270(
            first_name="JANE",
            last_name="DOE",
            dob=date(2010, 5, 5),
            relationship="19",
        )
    return ParseResult270(
        envelope=Envelope(
            sender_id="SUBMITTERID",
            receiver_id="RECEIVERID",
            control_number="000000001",
            transaction_date=date(2024, 1, 1),
            implementation_guide="005010X279A1",
        ),
        information_source=InformationSource270(name="PAYER NAME", id="SKCO0"),
        information_receiver=InformationReceiver270(name="PROVIDER NAME", npi="1234567890"),
        subscriber=Subscriber270(
            member_id="MEMBER123",
            first_name="JOHN",
            last_name="DOE",
            dob=date(1980, 1, 1),
        ),
        patient=patient,
        inquiries=[EligibilityBenefitInquiry(service_type_code=service_type_code)],
        summary=BatchSummary(
            input_file="round_trip.txt",
            total_claims=1,
            passed=1,
            failed=0,
        ),
    )


def test_serialize_270_envelope_segments():
    """Output starts with ``ISA*`` and ends with ``IEA*``; canonical envelope
    layers are all present in order.
    """
    text = serialize_270(_build_result())
    assert text.startswith("ISA*")
    # Strip trailing newline if any, then check the IEA tail.
    body = text.rstrip("\n")
    assert body.endswith("~IEA*1*000000001~")
    # Sender/receiver ids in the GS are padded to 15 chars (X12 fixed width).
    assert "GS*HC*SUBMITTERID    *RECEIVERID     *" in text
    assert "ST*270*0001*005010X279A1~" in text
    assert "BHT*0022*13*" in text
    assert "HL*1**20*1~" in text
    assert "NM1*PR*2*PAYER NAME" in text
    assert "HL*2*1*21*1~" in text
    assert "NM1*1P*2*PROVIDER NAME" in text
    assert "HL*3*2*22*0~" in text
    assert "TRN*2*TRACE001~" in text
    assert "NM1*IL*1*DOE*JOHN" in text
    assert "DMG*D8*19800101" in text
    assert "SE*" in text
    assert "GE*1*1        ~" in text  # group_control_number padded to 9 chars
    assert "IEA*1*000000001~" in text


def test_serialize_270_eq_segment_present():
    """Output contains a single ``EQ*`` segment carrying the service type code."""
    text = serialize_270(_build_result(service_type_code="30"))
    assert "EQ*30~" in text


def test_serialize_270_minimal_round_trip():
    """Build → serialize → parse → assert fields match (envelope + subscriber + EQ)."""
    original = _build_result(service_type_code="1")
    text = serialize_270(original)
    re_parsed = parse(text, input_file="round_trip.txt")

    # Envelope
    assert re_parsed.envelope.sender_id.strip() == original.envelope.sender_id
    assert re_parsed.envelope.receiver_id.strip() == original.envelope.receiver_id
    assert re_parsed.envelope.control_number == "000000001"
    assert re_parsed.envelope.implementation_guide == "005010X279A1"

    # Information source / receiver
    assert re_parsed.information_source.name == "PAYER NAME"
    assert re_parsed.information_source.id == "SKCO0"
    assert re_parsed.information_receiver.name == "PROVIDER NAME"
    assert re_parsed.information_receiver.npi == "1234567890"

    # Subscriber
    assert re_parsed.subscriber.first_name == "JOHN"
    assert re_parsed.subscriber.last_name == "DOE"
    assert re_parsed.subscriber.member_id == "MEMBER123"
    assert re_parsed.subscriber.dob == date(1980, 1, 1)

    # Inquiry
    assert len(re_parsed.inquiries) == 1
    assert re_parsed.inquiries[0].service_type_code == "1"


def test_serialize_270_with_patient_round_trip():
    """Build a result with a Patient270, serialize, re-parse, assert the
    patient loop comes back.
    """
    original = _build_result(with_patient=True)
    text = serialize_270(original)
    re_parsed = parse(text, input_file="round_trip_with_patient.txt")
    assert re_parsed.patient is not None
    assert re_parsed.patient.first_name == "JANE"
    assert re_parsed.patient.last_name == "DOE"
    assert re_parsed.patient.dob == date(2010, 5, 5)


def test_serialize_270_custom_interchange_control_number():
    """A custom interchange control number propagates into the ISA and IEA segments."""
    text = serialize_270(_build_result(), interchange_control_number="000000042")
    assert "ISA*00*          *00*          *ZZ*SUBMITTERID    *ZZ*RECEIVERID" in text
    # IEA*1*<ICN>~ at the end.
    assert text.rstrip("\n").endswith("~IEA*1*000000042~")
    # ISA also carries it.
    assert "*000000042*" in text.split("~", 1)[0]


# --------------------------------------------------------------------------- #
# T25 — Cross-parser round-trip (full)
# --------------------------------------------------------------------------- #


def test_cross_parser_270_round_trip():
    """Build a fully populated ParseResult270, serialize, parse, assert each
    field matches the original (except the raw envelope control_number
    which the serializer regenerates from defaults).
    """
    original = _build_result(
        service_type_code="88", with_patient=True,
    )
    text = serialize_270(original)
    re_parsed = parse(text, input_file="cross.txt")

    # Envelope — sender/receiver/guide are stable; control_number is regenerated
    # by the serializer's default "000000001" (the parser pulls it from ISA13).
    assert re_parsed.envelope.control_number == "000000001"
    assert re_parsed.envelope.sender_id.strip() == "SUBMITTERID"
    assert re_parsed.envelope.receiver_id.strip() == "RECEIVERID"
    assert re_parsed.envelope.implementation_guide == "005010X279A1"

    # Information source + receiver
    assert re_parsed.information_source.name == original.information_source.name
    assert re_parsed.information_source.id == original.information_source.id
    assert re_parsed.information_receiver.name == original.information_receiver.name
    assert re_parsed.information_receiver.npi == original.information_receiver.npi

    # Subscriber
    assert re_parsed.subscriber.member_id == original.subscriber.member_id
    assert re_parsed.subscriber.first_name == original.subscriber.first_name
    assert re_parsed.subscriber.last_name == original.subscriber.last_name
    assert re_parsed.subscriber.dob == original.subscriber.dob

    # Patient (set on the original).
    assert re_parsed.patient is not None
    assert re_parsed.patient.first_name == original.patient.first_name
    assert re_parsed.patient.last_name == original.patient.last_name
    assert re_parsed.patient.dob == original.patient.dob

    # Inquiries
    assert len(re_parsed.inquiries) == len(original.inquiries)
    for got, exp in zip(re_parsed.inquiries, original.inquiries):
        assert got.service_type_code == exp.service_type_code

    # Re-serializing the parsed result must be identical to the first
    # serialization (idempotent round-trip — except for BHT03 which is
    # timestamp-based, and ISA09/10/GS04/05 which use the current date).
    text2 = serialize_270(re_parsed)
    # Normalize both texts to drop the date/time-driven segments before
    # comparing for "shape" stability: we keep envelope, HL/NM1, EQ.
    def _shape(t: str) -> list[str]:
        return [
            s for s in t.split("~")
            if s and not s.startswith("BHT") and not s.startswith("ISA")
            and not s.startswith("GS") and not s.startswith("GE")
            and not s.startswith("IEA")
        ]

    assert _shape(text) == _shape(text2)
