"""Classifier three-tier tests (CONTRACT §5)."""

from __future__ import annotations

from app.matching.classifier import classify
from app.matching.models import NameMapEntry, PlannedLine
from tests.conftest import make_invoice


def _line(row, name, amount):
    return PlannedLine(source_row=row, client_name=name, planned_amount=amount)


def _name_map():
    return [
        NameMapEntry("Baskit (Duke)", 101, "Baskit Pty Ltd"),
        NameMapEntry("AuditWhiz (Big Pond)", 102, "Big Pond Holdings"),
        NameMapEntry("Vanguard Claims", 112, "Vanguard Insurance"),
        NameMapEntry("Quarterly Co", 120, "Quarterly Holdings"),
    ]


def _by_row(results):
    return {r.source_row: r for r in results if r.source_row is not None}


def test_green_red_amber_and_billed_unplanned(settings):
    planned = [
        _line(2, "Baskit (Duke)", 40000),  # exact invoice -> green
        _line(3, "AuditWhiz (Big Pond)", 30000),  # invoice 18000 -> amber (partial)
        _line(4, "Vanguard Claims", 35000),  # no invoice -> red
    ]
    invoices = [
        make_invoice(9001, "Baskit Pty Ltd", 40000, customer_id=101),
        make_invoice(9002, "Big Pond Holdings", 18000, customer_id=102),
        make_invoice(9099, "Walk-in Software Client", 7777, customer_id=199),
    ]
    results = classify(planned, invoices, _name_map(), [], settings)
    by_row = _by_row(results)

    assert by_row[2].status == "green"
    assert by_row[2].tier == "deterministic"
    assert by_row[2].variance == 0.0
    assert by_row[2].matched_invoice_id == 9001

    assert by_row[3].status == "amber"
    assert by_row[3].matched_invoice_id == 9002
    assert by_row[3].variance == 12000.0  # 30000 - 18000

    assert by_row[4].status == "red"
    assert by_row[4].tier == "deterministic"

    unplanned = [r for r in results if r.status == "billed_unplanned"]
    assert len(unplanned) == 1
    assert unplanned[0].tier == "reverse"
    assert unplanned[0].matched_invoice_id == 9099


def test_green_within_tolerance(settings):
    planned = [_line(2, "Baskit (Duke)", 15000)]
    invoices = [make_invoice(1, "Baskit Pty Ltd", 15000.40, customer_id=101)]
    results = classify(planned, invoices, [NameMapEntry("Baskit (Duke)", 101, "Baskit Pty Ltd")], [], settings)
    assert results[0].status == "green"  # 0.40 <= max(1, 75)


def test_unresolved_client_defaults_to_amber(settings):
    planned = [_line(2, "Mystery Client", 10000)]
    invoices = [make_invoice(1, "Totally Different Ltd", 10000, customer_id=500)]
    results = classify(planned, invoices, [], [], settings)
    line_results = [r for r in results if r.source_row == 2]
    assert line_results[0].status == "amber"
    assert line_results[0].tier == "agent"
    assert line_results[0].reasoning and "nresolved" in line_results[0].reasoning


def test_quarterly_rule_suppresses_red_in_off_month(settings):
    # October (month 10) is NOT a quarter month -> red suppressed to green.
    planned = [_line(2, "Quarterly Co", 33000)]
    rules = [
        {
            "scope": "Quarterly Co",
            "rule_type": "quarterly",
            "params_json": {"months": [3, 6, 9, 12]},
            "note": "Bills quarterly.",
        }
    ]
    results = classify(planned, [], _name_map(), rules, settings)
    line_result = next(r for r in results if r.source_row == 2)
    assert line_result.status == "green"
    assert line_result.reasoning == "Bills quarterly."


def test_quarterly_rule_keeps_red_in_on_month(settings):
    settings.period_month = 9  # September IS a quarter month -> still red.
    planned = [_line(2, "Quarterly Co", 33000)]
    rules = [
        {"scope": "Quarterly Co", "rule_type": "quarterly", "params_json": {"months": [3, 6, 9, 12]}}
    ]
    results = classify(planned, [], _name_map(), rules, settings)
    line_result = next(r for r in results if r.source_row == 2)
    assert line_result.status == "red"


def test_partial_rule_preexplains_amber(settings):
    planned = [_line(2, "AuditWhiz (Big Pond)", 30000)]
    invoices = [make_invoice(1, "Big Pond Holdings", 18000, customer_id=102)]
    rules = [{"scope": "AuditWhiz (Big Pond)", "rule_type": "partial", "params_json": {}, "note": "Monthly partial."}]
    results = classify(planned, invoices, _name_map(), rules, settings)
    r = next(x for x in results if x.source_row == 2)
    assert r.status == "amber"
    assert r.reasoning == "Monthly partial."


def test_ignore_rule_drops_line(settings):
    planned = [_line(2, "Noise Co", 5000)]
    rules = [{"scope": "Noise Co", "rule_type": "ignore", "params_json": {}}]
    results = classify(planned, [], [], rules, settings)
    assert all(r.source_row != 2 for r in results)


def test_adjudicate_callback_is_used_for_residue(settings):
    from app.matching.models import MatchResult

    def fake_adjudicate(line, invoices):
        return MatchResult(status="amber", tier="agent", confidence=0.9, reasoning="bundled by agent")

    planned = [_line(2, "Mystery Client", 10000)]
    invoices = [make_invoice(1, "Something", 9000, customer_id=7)]
    results = classify(planned, invoices, [], [], settings, adjudicate=fake_adjudicate)
    r = next(x for x in results if x.source_row == 2)
    assert r.reasoning == "bundled by agent"
    assert r.confidence == 0.9
    assert r.source_row == 2  # filled in by classifier
