"""Explicit re-stamp — the ONLY other money writer besides the import
commit, and it goes through the SAME stamping.py resolution (04
"Money paths": re-stamp is an explicit action; scoped; reports counts).

Semantics pinned by the test plan:
  - stamps come from compensation_terms in force TODAY + rate_overrides
    precedence (worker-specific > project-wide > term) — stamping.py
    owns all of that; this module only orchestrates;
  - a row with NO worker resolution gets NULL stamps and is included
    in the report (T-ADJ-014, explicit);
  - NULL rate -> NULL stamp on every path — never coalesced to 0
    (T-MNY-001 / judgment #19);
  - the reported count is rows whose stamp VALUES changed (T-ADJ-015
    honest counts).

Lock handling is the CALLER's job (bulk recalculate skips invoiced rows
for everyone — T-ADJ-017; a single-row owner edit may re-stamp a locked
row it is allowed to edit).
"""

from __future__ import annotations

import uuid
from decimal import Decimal

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models import Engagement, Party, TimeEntry
from app.services.money.stamping import (
    WORKER_ENGAGEMENT_TYPES,
    StampRates,
    resolve_rates,
    stamp_entry,
)

_NO_RATES = StampRates(None, None, None, None)


def resolve_engagement_for_party(
    session: Session, tenant_id: uuid.UUID, worker_party_id: uuid.UUID
) -> uuid.UUID | None:
    """party -> its active worker engagement in the tenant (earliest
    created wins — the by-party twin of stamping.resolve_worker)."""
    return session.scalar(
        select(Engagement.id)
        .join(Party, Party.id == Engagement.party_b_id)
        .where(
            Engagement.tenant_id == tenant_id,
            Engagement.type.in_(WORKER_ENGAGEMENT_TYPES),
            Engagement.ended_on.is_(None),
            Engagement.party_b_id == worker_party_id,
            Party.kind == "person",
        )
        .order_by(Engagement.created_at)
    )


def _as_decimal(value) -> Decimal | None:
    return None if value is None else Decimal(str(value))


def restamp_entry(
    session: Session,
    entry: TimeEntry,
    *,
    rate_cache: dict[tuple[uuid.UUID | None, uuid.UUID | None], StampRates] | None = None,
) -> bool:
    """Re-stamp ONE row from current terms/overrides. Returns True when
    a stamp value actually changed. Unresolved worker -> NULL stamps."""
    if entry.worker_party_id is not None and entry.via_engagement_id is None:
        # attribution set without an engagement (shouldn't happen via the
        # app's own writers) — re-derive rather than stamping blind
        entry.via_engagement_id = resolve_engagement_for_party(
            session, entry.tenant_id, entry.worker_party_id
        )

    if entry.worker_party_id is None or entry.via_engagement_id is None:
        rates = _NO_RATES
    else:
        key = (entry.via_engagement_id, entry.project_id)
        if rate_cache is not None and key in rate_cache:
            rates = rate_cache[key]
        else:
            rates = resolve_rates(
                session,
                entry.tenant_id,
                entry.via_engagement_id,
                entry.worker_party_id,
                entry.project_id,
            )
            if rate_cache is not None:
                rate_cache[key] = rates

    stamp = stamp_entry(entry.duration_seconds, rates)
    before = (
        _as_decimal(entry.cost_amount),
        entry.cost_currency,
        _as_decimal(entry.billout_amount),
        entry.billout_currency,
    )
    after = (stamp.cost_amount, stamp.cost_currency, stamp.billout_amount, stamp.billout_currency)
    entry.cost_amount = stamp.cost_amount
    entry.cost_currency = stamp.cost_currency
    entry.billout_amount = stamp.billout_amount
    entry.billout_currency = stamp.billout_currency
    return before != after
