"""Tests for the 999 ACK parser orchestrator."""
from __future__ import annotations

from datetime import date
from pathlib import Path

import pytest

from cyclone.parsers.exceptions import CycloneParseError
from cyclone.parsers.parse_999 import parse_999_text

ACCEPTED = Path(__file__).parent / "fixtures" / "minimal_999.txt"
REJECTED = Path(__file__).parent / "fixtures" / "minimal_999_rejected.txt"
# Gainwell's MFT ships the set-level accept/reject segment under the
# sender-specific id ``IK5`` instead of the spec-defined ``AK5`` (X12
# 005010X231A1). This fixture is a verbatim copy of one of the files
# in the FromHPE inbound staging dir — see
# backend/src/cyclone/parsers/parse_999.py for the rationale.
GAINWELL_IK5 = Path(__file__).parent / "fixtures" / "minimal_999_ik5_gainwell.txt"


def test_parse_minimal_999_returns_accepted():
    """The minimal accepted fixture must surface an 'A' ack code on the
    functional group and a single accepted set."""
    text = ACCEPTED.read_text()
    result = parse_999_text(text, input_file=ACCEPTED.name)
    assert len(result.functional_group_acks) == 1
    fg = result.functional_group_acks[0]
    assert fg.ack_code == "A"
    assert fg.received_count == 1
    assert fg.accepted_count == 1
    assert fg.rejected_count == 0
    assert fg.ak1.functional_id_code == "HC"
    assert fg.ak1.group_control_number == "0001"


def test_parse_999_set_response_accepted():
    """The single set response is an AK5=A (accepted)."""
    text = ACCEPTED.read_text()
    result = parse_999_text(text, input_file=ACCEPTED.name)
    assert len(result.set_responses) == 1
    s = result.set_responses[0]
    assert s.set_accept_reject.code == "A"
    assert s.transaction_set_identifier == "837"
    assert s.set_control_number == "0001"
    assert s.ak2.functional_id_code == "837"
    assert s.segment_errors == []  # no AK3/AK4 in the accepted fixture


def test_parse_999_envelope_built():
    """Envelope must capture sender/receiver/control number from ISA/GS/ST."""
    text = ACCEPTED.read_text()
    result = parse_999_text(text, input_file=ACCEPTED.name)
    env = result.envelope
    assert env.sender_id == "SUBMITTERID"    # ISA06
    assert env.receiver_id == "RECEIVERID"   # ISA08
    assert env.control_number == "000000001"  # ISA13
    assert env.implementation_guide == "005010X231A1"
    # transaction_date parsed from the GS04 (20240101)
    assert env.transaction_date == date(2024, 1, 1)


def test_parse_999_ak3_ak4_optional():
    """A 999 with no AK3/AK4 segments parses cleanly with empty errors."""
    text = ACCEPTED.read_text()
    result = parse_999_text(text, input_file=ACCEPTED.name)
    for s in result.set_responses:
        assert s.segment_errors == []


def test_parse_999_rejected_set():
    """The rejected fixture has AK5=R; the parser must surface it."""
    text = REJECTED.read_text()
    result = parse_999_text(text, input_file=REJECTED.name)
    assert len(result.functional_group_acks) == 1
    fg = result.functional_group_acks[0]
    assert fg.ack_code == "R"
    assert fg.rejected_count == 1
    assert fg.accepted_count == 0
    s = result.set_responses[0]
    assert s.set_accept_reject.code == "R"


def test_parse_999_garbage_raises():
    """Non-EDI input must raise CycloneParseError, not return a half-built result."""
    with pytest.raises(CycloneParseError):
        parse_999_text("not edi at all", input_file="bad.txt")


def test_parse_999_gainwell_ik5_segment_accepted():
    """The IK5 set-level segment Gainwell ships must parse as 'A'.

    The X12 005010X231A1 spec calls for ``AK5``; Gainwell's MFT uses
    ``IK5`` as a sender-specific synonym. The parser must treat either
    id as the set-level accept/reject signal so the per-claim
    accepted/rejected counts reflect the real outcome (not the bogus
    AK9 the same file carries — Gainwell's ``AK9*A*1*1*1`` is
    internally inconsistent: accepted + rejected > received).
    """
    text = GAINWELL_IK5.read_text()
    result = parse_999_text(text, input_file=GAINWELL_IK5.name)
    assert len(result.set_responses) == 1
    s = result.set_responses[0]
    assert s.set_accept_reject.code == "A"
    assert s.transaction_set_identifier == "837"
    assert s.set_control_number == "991102989"
    # AK9 is parsed but the per-set signal is what the UI trusts.
    assert result.functional_group_acks[0].ack_code == "A"
    assert result.functional_group_acks[0].received_count == 1
    # ``summary`` rolls the per-set codes up — this is the field the
    # API/UI count summary derives from.
    assert result.summary.passed == 1
    assert result.summary.failed == 0
