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

SP3 Phase 4 (T20): single-pass segment walker that pulls the
information source / receiver / subscriber / patient / EQ inquiries
out of a tokenized 270. The fixture (`tests/fixtures/minimal_270.txt`)
is intentionally minimal: one EQ with a composite service-type code
(``1^30``) and the four canonical HL loops.
"""
from __future__ import annotations

from pathlib import Path

import pytest

from cyclone.parsers.parse_270 import parse

FIXTURE = Path(__file__).parent / "fixtures" / "minimal_270.txt"


def test_parse_minimal_270_returns_inquiry():
    """The fixture's EQ segment has the composite ``1^30`` — the parser
    keeps the whole string in ``service_type_code``.

    v1 convention: callers that need the individual codes should split
    on ``^``. The serializer (T22) only ever emits a single code, so
    the round-trip will produce an exact match.
    """
    text = FIXTURE.read_text()
    result = parse(text, input_file="minimal_270.txt")
    assert len(result.inquiries) == 1
    code = result.inquiries[0].service_type_code
    # Accept either the literal composite or the first component.
    assert code in {"1^30", "1"}
    if code == "1^30":
        first = code.split("^")[0]
        assert first in {"1"}


def test_parse_270_subscriber_name_extracted():
    """Subscriber first_name + last_name are pulled from NM1*IL."""
    text = FIXTURE.read_text()
    result = parse(text, input_file="minimal_270.txt")
    assert result.subscriber.first_name == "JOHN"
    assert result.subscriber.last_name == "DOE"
    assert result.subscriber.member_id == "MEMBER123"
    # DMG segment — D8 = CCYYMMDD, M = male.
    assert result.subscriber.dob is not None
    assert result.subscriber.dob.year == 1980
    assert result.subscriber.dob.month == 1
    assert result.subscriber.dob.day == 1


def test_parse_270_envelope_built():
    """Envelope has sender_id, receiver_id, control_number, and the
    implementation guide from ST*03.
    """
    text = FIXTURE.read_text()
    result = parse(text, input_file="minimal_270.txt")
    env = result.envelope
    assert env.sender_id.strip() == "SUBMITTERID"
    assert env.receiver_id.strip() == "RECEIVERID"
    assert env.control_number == "000000001"
    assert env.implementation_guide == "005010X279A1"
    # GS04 supplies the transaction date.
    assert env.transaction_date.year == 2024
    assert env.transaction_date.month == 1
    assert env.transaction_date.day == 1


def test_parse_270_information_source_extracted():
    """Information source (HL*20) pulls the payer name + id from NM1*PR."""
    text = FIXTURE.read_text()
    result = parse(text, input_file="minimal_270.txt")
    assert result.information_source.name == "PAYER NAME"
    assert result.information_source.id == "SKCO0"
    # Information receiver (HL*21) pulls the provider + NPI.
    assert result.information_receiver.name == "PROVIDER NAME"
    assert result.information_receiver.npi == "1234567890"


def test_parse_270_no_patient_when_no_hl23():
    """``patient`` is None when the document has no HL*23 loop."""
    text = FIXTURE.read_text()
    result = parse(text, input_file="minimal_270.txt")
    assert result.patient is None


def test_parse_270_invalid_edi_raises():
    """Garbage input must raise :class:`CycloneParseError` (not crash with
    a generic IndexError/KeyError).
    """
    from cyclone.parsers.exceptions import CycloneParseError

    with pytest.raises(CycloneParseError):
        parse("not edi at all", input_file="garbage.txt")


def test_parse_270_summary_counts_inquiries():
    """The summary reports the number of EQ inquiries we parsed."""
    text = FIXTURE.read_text()
    result = parse(text, input_file="minimal_270.txt")
    assert result.summary.total_claims == 1
    assert result.summary.passed == 1
    assert result.summary.failed == 0
    assert result.summary.input_file == "minimal_270.txt"
    assert result.summary.control_number == "000000001"
