"""TDD: compute_lanes — four Inbox lanes from the DB on read.

Plan: docs/superpowers/plans/2026-06-20-cyclone-workflow-automation.md
Task 6 (T6).
"""
from __future__ import annotations

from datetime import datetime, timedelta, timezone
from decimal import Decimal

import pytest

from cyclone import db
from cyclone.db import (
    Base, Batch, Claim, ClaimState, Remittance, init_db,
)
from cyclone.inbox_lanes import compute_lanes


@pytest.fixture(autouse=True)
def _setup(tmp_path, monkeypatch):
    monkeypatch.setenv("CYCLONE_DB_URL", f"sqlite:///{tmp_path}/lanes.db")
    db._reset_for_tests()
    db.init_db()
    yield


def _add_batch() -> None:
    with db.SessionLocal()() as s:
        if s.get(Batch, "B-1") is not None:
            return
        s.add(Batch(
            id="B-1", kind="837p", input_filename="x.txt",
            parsed_at=datetime.now(timezone.utc),
            totals_json={"total_claims": 1},
            validation_json={"passed": True, "warnings": [], "errors": []},
            raw_result_json={"_": "stub"},
        ))
        s.commit()


def _add_claim(*, claim_id: str, pcn: str = "PCN",
               state: ClaimState = ClaimState.SUBMITTED,
               charge: Decimal = Decimal("100.00"),
               provider_npi: str | None = "1234567890",
               payer_id: str | None = "PAYER-A",
               service_date=None) -> None:
    _add_batch()
    with db.SessionLocal()() as s:
        s.add(Claim(
            id=claim_id, batch_id="B-1",
            patient_control_number=pcn, state=state,
            charge_amount=charge, provider_npi=provider_npi,
            payer_id=payer_id, service_date_from=service_date,
        ))
        s.commit()


def _add_remit(*, remit_id: str, pcn: str = "PCN",
               charge: Decimal = Decimal("100.00"),
               claim_id: str | None = None,
               payer_id: str | None = "PAYER-A",
               service_date=None) -> None:
    _add_batch()
    with db.SessionLocal()() as s:
        r = Remittance(
            id=remit_id, batch_id="B-1",
            payer_claim_control_number=pcn,
            claim_id=claim_id,
            status_code="1", status_label="Processed",
            total_charge=charge,
            total_paid=Decimal("0"),
            received_at=datetime.now(timezone.utc),
            service_date=service_date,
        )
        # Stash rendering_provider_npi and payer_id in raw_json for the
        # 835 fields that aren't on the ORM model.
        r.raw_json = {
            "rendering_provider_npi": "1234567890",
            "payer_id": payer_id,
            "patient_control_number": pcn,
        }
        s.add(r)
        s.commit()


def test_rejected_lane_includes_rejected_claims():
    _add_claim(claim_id="C1", state=ClaimState.REJECTED)
    with db.SessionLocal()() as s:
        lanes = compute_lanes(s, dismissed_pairs=set())
    assert "C1" in {r["id"] for r in lanes.rejected}


def test_unmatched_lane_includes_submitted_claims_with_no_remit():
    _add_claim(claim_id="C1", state=ClaimState.SUBMITTED)
    with db.SessionLocal()() as s:
        lanes = compute_lanes(s, dismissed_pairs=set())
    assert "C1" in {r["id"] for r in lanes.unmatched}


def test_unmatched_remit_appears_with_candidates():
    _add_claim(claim_id="C1", pcn="PCN-1", service_date=None)
    _add_remit(remit_id="R1", pcn="PCN-1")  # exact match → candidate
    with db.SessionLocal()() as s:
        lanes = compute_lanes(s, dismissed_pairs=set())
    assert any(r["id"] == "R1" for r in lanes.candidates)


def test_candidate_below_threshold_is_hidden():
    _add_claim(claim_id="C1", pcn="A", charge=Decimal("100.00"))
    _add_remit(remit_id="R1", pcn="Z",
               charge=Decimal("999.00"))   # everything mismatched
    with db.SessionLocal()() as s:
        lanes = compute_lanes(s, dismissed_pairs=set())
    assert not any(r["id"] == "R1" for r in lanes.candidates)


def test_dismissed_pair_excluded_from_candidates():
    _add_claim(claim_id="C1", pcn="PCN-1")
    _add_remit(remit_id="R1", pcn="PCN-1")
    with db.SessionLocal()() as s:
        lanes = compute_lanes(
            s, dismissed_pairs={frozenset({"C1", "R1"})},
        )
    assert not any(r["id"] == "R1" for r in lanes.candidates)


def test_done_today_includes_recent_terminal_states():
    _add_claim(claim_id="C1", state=ClaimState.PAID)
    _add_claim(claim_id="C2", state=ClaimState.PAID)
    # Force C1's state_changed_at to be recent, C2 to be old.
    now = datetime.now(timezone.utc)
    with db.SessionLocal()() as s:
        # Migration 0014: composite PK — filter-by-id returns the single
        # claim with that id (no cross-batch duplicates in this test).
        c1 = s.query(Claim).filter(Claim.id == "C1").first()
        c1.state_changed_at = now - timedelta(hours=2)
        c2 = s.query(Claim).filter(Claim.id == "C2").first()
        c2.state_changed_at = now - timedelta(hours=30)
        s.commit()

    with db.SessionLocal()() as s:
        lanes = compute_lanes(s, dismissed_pairs=set())
    ids = {r["id"] for r in lanes.done_today}
    assert "C1" in ids
    assert "C2" not in ids
