"""services/money/stamping — the write-path money contract
(06-test-plan §3.1/§3.2 subset that M4's import writes depend on):
T-CLK-021b/022b/023, T-MNY-001 (NULL = never priced, judgment #19),
T-TEAM-07/08 (NULL-rate vs Gary's zero), and the effective-billout
precedence (T-ADJ-001/002/003/004/008 semantics at the write path)."""

from datetime import date
from decimal import Decimal

from app.models import CompensationTerm
from app.services.money.stamping import (
    StampRates,
    compute_amount,
    resolve_rates,
    resolve_worker,
    stamp_entry,
)

ON = date(2026, 6, 15)


def rates_for(graph, worker_key, project=None, on=ON):
    parties = {"adi": graph.parties.adi, "gary": graph.parties.gary, "zed": graph.parties.zed}
    return resolve_rates(
        graph.session,
        graph.tenant.id,
        graph.engagements[worker_key].id,
        parties[worker_key].id,
        project.id if project is not None else None,
        on,
    )


# ------------------------------------------------------- pure amount math


def test_clk_022b_null_if_either_input_null():
    assert compute_amount(None, Decimal(25)) is None
    assert compute_amount(3600, None) is None
    assert compute_amount(None, None) is None


def test_clk_023_amount_is_hours_times_rate_2dp():
    assert compute_amount(3600, Decimal(25)) == Decimal("25.00")
    assert compute_amount(1800, Decimal(25)) == Decimal("12.50")
    assert compute_amount(60, Decimal(25)) == Decimal("0.42")  # 1min × $25 ≈ $0.42


# -------------------------------------------------- worker resolution


def test_resolve_worker_matches_active_engagement(graph):
    resolved = resolve_worker(graph.session, graph.tenant.id, "INFO@adipramono.com")
    assert resolved == (graph.parties.adi.id, graph.engagements["adi"].id)


def test_clk_018_unknown_email_resolves_nothing(graph):
    assert resolve_worker(graph.session, graph.tenant.id, "unknown@example.com") is None
    assert resolve_worker(graph.session, graph.tenant.id, "") is None


def test_resolve_worker_ignores_ended_engagements(graph):
    graph.engagements["adi"].ended_on = date(2026, 1, 31)
    graph.session.flush()
    assert resolve_worker(graph.session, graph.tenant.id, "info@adipramono.com") is None
    graph.engagements["adi"].ended_on = None
    graph.session.flush()


# ------------------------------------------------ terms + stamp semantics


def test_clk_021b_billout_term_in_force_stamps_the_entry(graph):
    rates = rates_for(graph, "adi", graph.projects["victoria_blocks"])
    assert rates.billout_rate == Decimal("25.00")
    assert rates.billout_currency == "CAD"
    stamp = stamp_entry(7200, rates)
    assert stamp.billout_amount == Decimal("50.00")
    assert stamp.billout_currency == "CAD"
    assert stamp.cost_amount == Decimal("36.00")  # 2h × 18
    assert stamp.cost_currency == "CAD"


def test_mny_001_no_term_means_null_never_zero(graph):
    """T-MNY-001 / T-TEAM-07 / T-ADJ-013: zed has NO terms — every
    stamp is NULL ('never priced'), not coalesced to 0."""
    rates = rates_for(graph, "zed", graph.projects["victoria_blocks"])
    assert rates.cost_rate is None and rates.billout_rate is None
    stamp = stamp_entry(36000, rates)
    assert stamp.cost_amount is None
    assert stamp.cost_currency is None
    assert stamp.billout_amount is None
    assert stamp.billout_currency is None


def test_team_08_gary_zero_cost_is_a_real_zero(graph):
    """Gary (cost 0, billout 25): 10h -> cost 0.00 (not NULL), billout
    250.00 — the zero-cost billable worker (judgment #12)."""
    rates = rates_for(graph, "gary", graph.projects["victoria_blocks"])
    stamp = stamp_entry(36000, rates)
    assert stamp.cost_amount == Decimal("0.00")
    assert stamp.cost_currency == "CAD"
    assert stamp.billout_amount == Decimal("250.00")


def test_term_out_of_force_is_null(graph):
    rates = rates_for(graph, "adi", graph.projects["victoria_blocks"], on=date(2025, 12, 31))
    assert rates.cost_rate is None and rates.billout_rate is None


def test_project_scoped_term_beats_engagement_default(graph):
    graph.session.add(
        CompensationTerm(
            engagement_id=graph.engagements["adi"].id,
            kind="billout",
            model="hourly",
            rate_amount=40,
            currency="CAD",
            project_id=graph.projects["victoria_blocks"].id,
            effective_from=date(2026, 1, 1),
        )
    )
    graph.session.flush()
    scoped = rates_for(graph, "adi", graph.projects["victoria_blocks"])
    assert scoped.billout_rate == Decimal("40.00")
    # the default still applies elsewhere (no override on this one)
    graph.session.rollback()


# -------------------------------------------- override precedence (write)


def test_adj_001_worker_specific_beats_project_wide(graph):
    """Brentwood:Website carries adi-specific 30 AND project-wide 28 —
    specific wins for adi."""
    rates = rates_for(graph, "adi", graph.projects["brentwood_website"])
    assert rates.billout_rate == Decimal("30.00")
    # cost is untouched by billout overrides
    assert rates.cost_rate == Decimal("18.00")


def test_adj_002_project_wide_applies_to_other_workers(graph):
    rates = rates_for(graph, "gary", graph.projects["brentwood_website"])
    assert rates.billout_rate == Decimal("28.00")


def test_adj_003_no_override_falls_back_to_default_term(graph):
    rates = rates_for(graph, "adi", graph.projects["victoria_blocks"])
    assert rates.billout_rate == Decimal("25.00")


def test_adj_004_pct_override_modifies_the_default(graph):
    """DaxTech:Website carries pct -20: default 25 -> 20.00."""
    rates = rates_for(graph, "adi", graph.projects["daxtech_website"])
    assert rates.billout_rate == Decimal("20.00")
    assert rates.billout_currency == "CAD"


def test_adj_008_pct_with_null_default_is_null(graph):
    """zed has no billout term — a pct override cannot conjure a rate
    ('no rate available'), never 0."""
    rates = rates_for(graph, "zed", graph.projects["daxtech_website"])
    assert rates.billout_rate is None
    assert rates.billout_currency is None


def test_unassigned_project_ignores_overrides(graph):
    rates = rates_for(graph, "adi", None)
    assert rates.billout_rate == Decimal("25.00")


def test_stamp_entry_currency_rides_with_amount():
    """02 §3: a stamp is an (amount, currency) PAIR — a null duration
    nulls the currency too, satisfying the DB pair constraint."""
    rates = StampRates(Decimal(18), "CAD", Decimal(25), "CAD")
    stamp = stamp_entry(None, rates)
    assert stamp.cost_amount is None and stamp.cost_currency is None
    assert stamp.billout_amount is None and stamp.billout_currency is None
