"""SP7 — pure-function tests for match_service_lines().

Spec §4.
"""
from dataclasses import dataclass
from datetime import date
from decimal import Decimal

from cyclone.reconcile import (
    LineMatch,
    match_service_lines,
)


@dataclass
class FakeLine:
    line_number: int
    procedure_code: str
    modifiers: list
    service_date: date | None
    units: Decimal | None
    payment: Decimal = Decimal("0")  # 835 only


@dataclass
class FakeClaimLine(FakeLine):
    charge: Decimal = Decimal("0")  # 837 only


def _claim(*lines: FakeClaimLine) -> list:
    return list(lines)


def _remit(*lines: FakeLine) -> list:
    return list(lines)


def test_exact_match_on_all_four_criteria():
    claim_lines = _claim(FakeClaimLine(1, "99213", [], date(2026, 6, 1), Decimal("1")))
    remit_lines = _remit(FakeLine(1, "99213", [], date(2026, 6, 1), Decimal("1"), payment=Decimal("80")))
    matches = match_service_lines(claim_lines, remit_lines)
    assert len(matches) == 1
    assert matches[0].claim_line.line_number == 1
    assert matches[0].service_payment.line_number == 1
    assert matches[0].status == "matched"


def test_procedure_code_mismatch_both_unmatched():
    claim_lines = _claim(FakeClaimLine(1, "99213", [], None, None))
    remit_lines = _remit(FakeLine(1, "99214", [], None, None))
    matches = match_service_lines(claim_lines, remit_lines)
    assert len(matches) == 2
    statuses = sorted(m.status for m in matches)
    assert statuses == ["unmatched_835_only", "unmatched_837_only"]


def test_modifiers_set_equal_regardless_of_order():
    claim_lines = _claim(FakeClaimLine(1, "99213", ["25", "59"], None, None))
    remit_lines = _remit(FakeLine(1, "99213", ["59", "25"], None, None))
    matches = match_service_lines(claim_lines, remit_lines)
    assert len(matches) == 1
    assert matches[0].status == "matched"


def test_service_date_mismatch():
    claim_lines = _claim(FakeClaimLine(1, "99213", [], date(2026, 6, 1), None))
    remit_lines = _remit(FakeLine(1, "99213", [], date(2026, 6, 2), None))
    matches = match_service_lines(claim_lines, remit_lines)
    assert len(matches) == 2
    statuses = sorted(m.status for m in matches)
    assert statuses == ["unmatched_835_only", "unmatched_837_only"]


def test_units_mismatch():
    claim_lines = _claim(FakeClaimLine(1, "99213", [], None, Decimal("1")))
    remit_lines = _remit(FakeLine(1, "99213", [], None, Decimal("2")))
    matches = match_service_lines(claim_lines, remit_lines)
    assert len(matches) == 2
    statuses = sorted(m.status for m in matches)
    assert statuses == ["unmatched_835_only", "unmatched_837_only"]


def test_procedure_code_case_normalized():
    claim_lines = _claim(FakeClaimLine(1, "99213", [], None, None))
    remit_lines = _remit(FakeLine(1, "99213", [], None, None))  # case irrelevant when both upper
    matches = match_service_lines(claim_lines, remit_lines)
    assert matches[0].status == "matched"


def test_claim_has_more_lines_than_remit():
    claim_lines = _claim(
        FakeClaimLine(1, "99213", [], None, None),
        FakeClaimLine(2, "99214", [], None, None),
    )
    remit_lines = _remit(FakeLine(1, "99213", [], None, None))
    matches = match_service_lines(claim_lines, remit_lines)
    statuses = sorted(m.status for m in matches)
    assert statuses == ["matched", "unmatched_837_only"]


def test_remit_has_more_lines_than_claim():
    claim_lines = _claim(FakeClaimLine(1, "99213", [], None, None))
    remit_lines = _remit(
        FakeLine(1, "99213", [], None, None),
        FakeLine(2, "99214", [], None, None),
    )
    matches = match_service_lines(claim_lines, remit_lines)
    statuses = sorted(m.status for m in matches)
    assert statuses == ["matched", "unmatched_835_only"]


def test_empty_837_lines_no_reconciliation_rows():
    matches = match_service_lines([], _remit(FakeLine(1, "99213", [], None, None)))
    assert len(matches) == 1
    assert matches[0].status == "unmatched_835_only"


def test_empty_835_lines_no_reconciliation_rows():
    matches = match_service_lines(_claim(FakeClaimLine(1, "99213", [], None, None)), [])
    assert len(matches) == 1
    assert matches[0].status == "unmatched_837_only"


def test_both_empty():
    assert match_service_lines([], []) == []
