"""Shared write-gate for the PlusROI write forms (phase 3, ADR #020/#021).

Every Clients / Contacts / Vendors mutation applies the SAME three-part
gate the invoice service pioneered (``services/money/invoices.py``), plus
tenant resolution:

  1. **owner** — ``can_transfer`` (the money-visibility owner gate); a
     non-owner is refused BEFORE any read or write.
  2. **writable books** — refused (409 ``SHEET_OWNED_READ_ONLY``) while the
     tenant's books are SHEET-owned, because the nightly rebuild would
     silently overwrite the write (ADR #019/#021). Keyed on the tenant's
     effective ``books_owner`` via ``services.tenancy.effective_books_owner``
     — the SAME predicate the invoice write-block uses, so the two lift
     together at a flip with no code change.
  3. **tenant** — a resolved ``actor.tenant_id`` (every row these services
     touch is tenant-scoped).

Reads reuse ``require_owner`` + ``require_tenant`` only (a read never
refuses on sheet-ownership and is never audited).
"""

from __future__ import annotations

import uuid

from sqlalchemy.orm import Session

from app.models import Tenant
from app.services.errors import ServiceError
from app.services.tenancy import BOOKS_OWNER_SHEET, effective_books_owner


def require_owner(actor, message: str = "Only an owner can manage this.") -> None:
    """Owner-only gate (``can_transfer``) — parity with the invoice/team
    money gates. A non-owner (manager / worker / none) is refused."""
    if not actor.allowed("can_transfer"):
        raise ServiceError(403, "NOT_ALLOWED", message)


def require_tenant(actor) -> uuid.UUID:
    if actor.tenant_id is None:
        raise ServiceError(400, "NO_TENANT", "No tenant context resolved.")
    return actor.tenant_id


def require_writable_books(session: Session, actor) -> None:
    """Sheet-owned write-block (mirrors ``money/invoices._require_writable_books``,
    ADR #021). While the tenant's books are SHEET-owned, every mutation is
    refused: an edit would look saved, then vanish at the next nightly
    rebuild. The block lifts when the tenant is flipped app-owned — no code
    change (``scripts/flip_books_owner.py``)."""
    tenant = session.get(Tenant, actor.tenant_id)
    if tenant is not None and effective_books_owner(tenant.settings) == BOOKS_OWNER_SHEET:
        raise ServiceError(
            409,
            "SHEET_OWNED_READ_ONLY",
            "These books are sheet-owned — the sheet is the source of truth.",
            "The app rebuilds these books nightly from the Google Sheet, so an"
            " edit made here would be overwritten tonight. Make the change in"
            " the sheet; editing unlocks when the books are flipped app-owned.",
        )
