"""GATE — the append-only rate-history invariant, proven on a clone of
the REAL migrated data with a real worker (Adi).

The invariant (02 §2, 02 §5 invariant 1; ADR #014 lineage): a rate change
NEVER mutates the in-force term's amount, and NEVER rewrites the money
already stamped onto historical time_entries. Instead it:

  1. ends the current term (stamps effective_to), and
  2. opens a NEW term effective-dated from the change,

so historical resolution still returns the old rate, existing entry
stamps are byte-for-byte unchanged, and the NEXT import/stamp resolves
the new rate. This runs on the sidecar Postgres migrated shape (the same
schema + real Adi engagement production has), inside a rolled-back
SAVEPOINT so the service's real commit is observed and then discarded.
"""

from datetime import date, timedelta
from decimal import Decimal

from sqlalchemy import select

from app.models import CompensationTerm, TimeEntry
from app.services import team_admin
from app.services.audit import snapshot_entry
from app.services.money import restamp_entry
from app.services.money.stamping import resolve_rates
from tests.team_admin.conftest import actor_named, real_worker_engagement

ADI_EMAIL = "info@adipramono.com"


def _cost_terms(session, engagement_id):
    return session.scalars(
        select(CompensationTerm)
        .where(
            CompensationTerm.engagement_id == engagement_id,
            CompensationTerm.kind == "cost",
            CompensationTerm.project_id.is_(None),
        )
        .order_by(CompensationTerm.effective_from)
    ).all()


def test_rate_change_is_append_only_on_a_real_worker(s):
    party, eng = real_worker_engagement(s, ADI_EMAIL)
    rian = actor_named(s, "rian")

    # baseline: the migrated cost term (rate 14, open, effective_from = min
    # entry date 2026-03-01) and Adi's stamped entries.
    before_terms = _cost_terms(s, eng.id)
    assert len(before_terms) == 1
    original = before_terms[0]
    assert original.rate_amount == Decimal("14.00")
    assert original.effective_to is None
    historical_from = original.effective_from

    entries = s.scalars(
        select(TimeEntry).where(TimeEntry.worker_party_id == party.id).order_by(TimeEntry.id)
    ).all()
    assert entries, "expected migrated entries for Adi"
    stamps_before = {e.id: snapshot_entry(e) for e in entries}

    # THE CHANGE — cost 14 -> 22, effective today.
    res = team_admin.update_worker(s, rian, party.id, {"cost_rate": "22"})
    assert "cost" in res["rate_changes"]

    # (1) append, not overwrite: a new open term + the old one closed.
    after_terms = _cost_terms(s, eng.id)
    assert len(after_terms) == 2
    closed = [t for t in after_terms if t.effective_to is not None]
    open_ = [t for t in after_terms if t.effective_to is None]
    assert len(closed) == 1 and len(open_) == 1
    assert closed[0].rate_amount == Decimal("14.00")
    assert closed[0].effective_from == historical_from
    assert closed[0].effective_to == date.today() - timedelta(days=1)
    assert open_[0].rate_amount == Decimal("22.00")
    assert open_[0].effective_from == date.today()

    # (2) existing entry stamps byte-for-byte unchanged (re-stamp is the
    # explicit /entries action, deliberately NOT triggered here).
    for e in s.scalars(
        select(TimeEntry).where(TimeEntry.worker_party_id == party.id)
    ).all():
        assert snapshot_entry(e) == stamps_before[e.id]

    # (3) history preserved: a March date still resolves the OLD 14 rate.
    hist = resolve_rates(s, eng.tenant_id, eng.id, party.id, None, on=date(2026, 3, 15))
    assert hist.cost_rate == Decimal("14")

    # (4) the NEXT import stamps at the NEW rate. Resolve today, and stamp a
    # fresh entry through the ONE money path to prove it end-to-end.
    today_rates = resolve_rates(s, eng.tenant_id, eng.id, party.id, None, on=date.today())
    assert today_rates.cost_rate == Decimal("22")

    fresh = TimeEntry(
        tenant_id=eng.tenant_id,
        worker_party_id=party.id,
        via_engagement_id=eng.id,
        project_id=None,
        source="manual",
        start_at=date.today().isoformat() + " 09:00:00",
        duration_seconds=3600,
        billable=True,
    )
    s.add(fresh)
    s.flush()
    restamp_entry(s, fresh)
    assert fresh.cost_amount == Decimal("22.00")  # 1h × 22


def test_clearing_a_rate_ends_the_term_and_next_stamp_is_null(s):
    party, eng = real_worker_engagement(s, ADI_EMAIL)
    rian = actor_named(s, "rian")

    team_admin.update_worker(s, rian, party.id, {"cost_rate": ""})  # clear
    open_cost = [t for t in _cost_terms(s, eng.id) if t.effective_to is None]
    assert open_cost == []  # no open cost term -> never priced going forward

    rates = resolve_rates(s, eng.tenant_id, eng.id, party.id, None, on=date.today())
    assert rates.cost_rate is None  # NULL, never coalesced to 0 (judgment #19)
