"""Sheet-owned write-block (ADR #019, re-keyed on books_owner by ADR #021).

While a tenant's books are SHEET-owned (settings.books_owner == "sheet" —
the nightly rebuilds them from the sheet export), EVERY invoice mutation
must refuse with 409 SHEET_OWNED_READ_ONLY — an edit would look saved,
then silently vanish at the next rebuild. A tenant still carrying only
the legacy ``parallel_run: true`` boolean reads as sheet-owned
(back-compat until the flip script rewrites it). App-owned ("app") and
legacy-mirrored (no flag — Bowden Works) tenants pass the guard.

Lightweight SQLite tests: the guard fires before any invoice row is even
loaded, so a stub actor + empty tables suffice; the heavyweight M6 suite
staying green proves the BW tenant (no flag) is unaffected.
"""

import uuid
from types import SimpleNamespace

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool

from app.models import Base, Party, Tenant
from app.services.bulk import BulkSelection
from app.services.errors import ServiceError
from app.services.money import invoices as svc


@pytest.fixture()
def s():
    engine = create_engine(
        "sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool
    )
    Base.metadata.create_all(engine)
    with Session(engine) as session:
        yield session
    engine.dispose()


def _tenant(s, *, settings: dict) -> Tenant:
    party = Party(kind="org", name="PlusROI", slug=f"plusroi-{uuid.uuid4().hex[:8]}")
    s.add(party)
    s.flush()
    tenant = Tenant(party_id=party.id, settings=settings)
    s.add(tenant)
    s.flush()
    return tenant


def _owner(tenant_id):
    return SimpleNamespace(
        tenant_id=tenant_id,
        user_id=None,
        real_user_id=None,
        is_viewing_as=False,
        allowed=lambda cap: True,
    )


MUTATIONS = [
    ("create", lambda s, a: svc.create_invoice(s, a, {"name": "X"})),
    ("update", lambda s, a: svc.update_invoice(s, a, uuid.uuid4(), {"name": "Y"})),
    ("delete", lambda s, a: svc.delete_invoice(s, a, uuid.uuid4())),
    (
        "apply",
        lambda s, a: svc.apply_entries(
            s, a, uuid.uuid4(), BulkSelection(expected_count=1, ids=[uuid.uuid4()])
        ),
    ),
    (
        "detach",
        lambda s, a: svc.detach_entries(
            s, a, ids=[uuid.uuid4()], expected_count=1
        ),
    ),
]

SHEET_OWNED_SETTINGS = [
    ("books_owner_sheet", {"books_owner": "sheet"}),
    ("legacy_parallel_run", {"parallel_run": True}),  # back-compat (ADR #021)
]


@pytest.mark.parametrize(
    "settings", [s for _, s in SHEET_OWNED_SETTINGS], ids=[n for n, _ in SHEET_OWNED_SETTINGS]
)
@pytest.mark.parametrize("name,mutate", MUTATIONS, ids=[m[0] for m in MUTATIONS])
def test_every_mutation_blocked_when_sheet_owned(s, name, mutate, settings):
    tenant = _tenant(s, settings=settings)
    with pytest.raises(ServiceError) as err:
        mutate(s, _owner(tenant.id))
    assert err.value.code == "SHEET_OWNED_READ_ONLY"
    assert err.value.status == 409


@pytest.mark.parametrize(
    "settings",
    [{}, {"books_owner": "app"}, {"parallel_run": False}],
    ids=["no_flag_bw", "app_owned", "parallel_run_false"],
)
def test_mutations_pass_the_guard_when_writable(s, settings):
    """Same calls on a writable tenant (legacy-mirrored BW, app-owned
    PlusROI, or an explicit parallel_run=False) get PAST the guard — they
    fail later (unknown invoice / no selection scope), never with the 409."""
    tenant = _tenant(s, settings=settings)
    for name, mutate in MUTATIONS:
        try:
            mutate(s, _owner(tenant.id))
        except ServiceError as err:
            assert err.code != "SHEET_OWNED_READ_ONLY", name
        except Exception:
            pass  # non-ServiceError downstream failures are fine here
