"""Vacate confirmation — setting a plot's status back to vacant ("Available")
while it still has an active linked record must be confirmed first (409,
PLOT_VACATE_DELETES_RECORD) rather than silently orphaning the record; on
confirmation the record is cascade-deleted (same soft-delete path as the
Records page). Covers all three status-changing endpoints: PATCH /plots/{id},
PATCH /plots/{id}/status, and PATCH /plots/{id}/revert.
"""
import uuid

import pytest
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.auth.models.user import User
from src.apps.plots.models.plot import Plot
from src.apps.records.models.burial_info import BurialInfo
from src.apps.records.models.record import Record
from src.apps.sales.models.contract import Contract
from src.apps.tenants.models.account import Account
from src.core.security import build_token_payload, create_access_token

pytestmark = pytest.mark.asyncio

PLOTS_URL = "/api/v1/plots"


async def _make_account(db: AsyncSession) -> Account:
    uid = uuid.uuid4().hex[:8]
    acc = Account(
        organization_name=f"Vacate Test {uid}",
        subdomain=f"vac-{uid}",
        contact_email=f"vac-{uid}@test.ca",
        plan="starter",
        status="active",
    )
    db.add(acc)
    await db.flush()
    return acc


async def _make_user(db: AsyncSession, account: Account, role: str = "administrator") -> User:
    user = User(
        tenant_id=account.id,
        email=f"{role}-{uuid.uuid4().hex[:6]}@test.ca",
        password_hash="x",
        first_name="T",
        last_name="U",
        role=role,
        status="active",
    )
    db.add(user)
    await db.flush()
    return user


def _headers(user: User, account: Account) -> dict:
    token = create_access_token(build_token_payload(user))
    return {"Authorization": f"Bearer {token}", "X-Tenant-ID": str(account.id)}


async def _make_plot_with_record(
    db: AsyncSession, account: Account, status: str = "reserved", interment_type: str = "burial",
) -> tuple[Plot, Record]:
    plot = Plot(tenant_id=account.id, plot_ref=f"P-{uuid.uuid4().hex[:6]}", status=status)
    db.add(plot)
    await db.flush()
    record = Record(
        tenant_id=account.id, plot_id=plot.id, first_name="Jane", last_name="Doe", status="active",
    )
    db.add(record)
    await db.flush()
    burial = BurialInfo(record_id=record.id, tenant_id=account.id, interment_type=interment_type)
    db.add(burial)
    await db.flush()
    return plot, record


async def test_patch_to_vacant_with_linked_record_returns_409_without_confirmation(
    client, db_session: AsyncSession,
):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot, record = await _make_plot_with_record(db_session, account)

    resp = await client.patch(
        f"{PLOTS_URL}/{plot.id}", json={"status": "vacant"}, headers=_headers(user, account),
    )
    assert resp.status_code == 409, resp.text
    body = resp.json()
    assert body["error_code"] == "PLOT_VACATE_DELETES_RECORD"
    assert "Jane Doe" in body["message"]

    await db_session.refresh(plot)
    assert plot.status == "reserved"
    await db_session.refresh(record)
    assert record.deleted_at is None


async def test_patch_to_vacant_with_confirmation_deletes_record(client, db_session: AsyncSession):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot, record = await _make_plot_with_record(db_session, account)

    resp = await client.patch(
        f"{PLOTS_URL}/{plot.id}",
        json={"status": "vacant", "confirm_delete_record": True},
        headers=_headers(user, account),
    )
    assert resp.status_code == 200, resp.text
    assert resp.json()["data"]["status"] == "vacant"

    await db_session.refresh(plot)
    assert plot.status == "vacant"
    await db_session.refresh(record)
    assert record.deleted_at is not None


async def test_confirm_delete_with_multiple_signed_contracts_on_plot_does_not_500(
    client, db_session: AsyncSession,
):
    """A plot can carry more than one signed contract over its lifetime (e.g.
    a companion/second interment) — confirming the vacate must not 500 when
    has_active_signed_contract()'s lookup finds more than one row."""
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot, record = await _make_plot_with_record(db_session, account)
    for i in range(2):
        db_session.add(Contract(
            tenant_id=account.id, plot_id=plot.id,
            contract_number=f"CNT-TEST-{uuid.uuid4().hex[:8]}-{i}", status="signed",
        ))
    await db_session.flush()

    resp = await client.patch(
        f"{PLOTS_URL}/{plot.id}",
        json={"status": "vacant", "confirm_delete_record": True},
        headers=_headers(user, account),
    )
    assert resp.status_code == 200, resp.text

    await db_session.refresh(plot)
    assert plot.status == "vacant"
    await db_session.refresh(record)
    assert record.deleted_at is not None


async def test_patch_to_vacant_without_linked_record_needs_no_confirmation(
    client, db_session: AsyncSession,
):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = Plot(tenant_id=account.id, plot_ref=f"P-{uuid.uuid4().hex[:6]}", status="hold")
    db_session.add(plot)
    await db_session.flush()

    resp = await client.patch(
        f"{PLOTS_URL}/{plot.id}", json={"status": "vacant"}, headers=_headers(user, account),
    )
    assert resp.status_code == 200, resp.text
    await db_session.refresh(plot)
    assert plot.status == "vacant"


async def test_change_status_endpoint_requires_confirmation_too(client, db_session: AsyncSession):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    # "reserved" (not "occupied") — an occupied/interred plot is now a
    # terminal status (see PLOT_STATUS_TRANSITIONS) and can never move back to
    # vacant at all, confirmed or not; this test is about the vacate
    # confirmation guard itself, which "reserved" -> "vacant" still exercises.
    plot, record = await _make_plot_with_record(
        db_session, account, status="reserved", interment_type="burial",
    )

    resp = await client.patch(
        f"{PLOTS_URL}/{plot.id}/status", json={"status": "vacant"}, headers=_headers(user, account),
    )
    assert resp.status_code == 409, resp.text
    assert resp.json()["error_code"] == "PLOT_VACATE_DELETES_RECORD"

    resp2 = await client.patch(
        f"{PLOTS_URL}/{plot.id}/status",
        json={"status": "vacant", "confirm_delete_record": True},
        headers=_headers(user, account),
    )
    assert resp2.status_code == 200, resp2.text
    await db_session.refresh(record)
    assert record.deleted_at is not None


async def test_revert_endpoint_requires_confirmation_too(client, db_session: AsyncSession):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot, record = await _make_plot_with_record(
        db_session, account, status="hold", interment_type="pre_need_no_service_yet",
    )

    resp = await client.patch(f"{PLOTS_URL}/{plot.id}/revert", headers=_headers(user, account))
    assert resp.status_code == 409, resp.text
    assert resp.json()["error_code"] == "PLOT_VACATE_DELETES_RECORD"

    await db_session.refresh(plot)
    assert plot.status == "hold"

    resp2 = await client.patch(
        f"{PLOTS_URL}/{plot.id}/revert",
        json={"confirm_delete_record": True},
        headers=_headers(user, account),
    )
    assert resp2.status_code == 200, resp2.text
    await db_session.refresh(record)
    assert record.deleted_at is not None
    await db_session.refresh(plot)
    assert plot.status == "vacant"
