"""Plot status transition rules (product decision, see PLOT_STATUS_TRANSITIONS
in src/core/constants.py):

  - occupied ("Interred") is terminal — cannot be changed to anything else.
  - reserved may only step back to vacant ("Available"), never to hold
    ("On hold"); moving forward to occupied is still allowed.
  - vacant and hold are unrestricted.

Covers all three status-writing 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.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"Transition Test {uid}",
        subdomain=f"trans-{uid}",
        contact_email=f"trans-{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(db: AsyncSession, account: Account, status: str) -> Plot:
    plot = Plot(tenant_id=account.id, plot_ref=f"P-{uuid.uuid4().hex[:6]}", status=status)
    db.add(plot)
    await db.flush()
    return plot


# ── occupied is terminal ─────────────────────────────────────────────────────

@pytest.mark.parametrize("target", ["vacant", "hold", "reserved"])
async def test_occupied_plot_cannot_change_status_via_update(
    client, db_session: AsyncSession, target: str,
):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = await _make_plot(db_session, account, status="occupied")

    resp = await client.patch(
        f"{PLOTS_URL}/{plot.id}", json={"status": target}, headers=_headers(user, account),
    )
    assert resp.status_code == 422, resp.text
    assert "terminal" in resp.json()["message"].lower()

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


@pytest.mark.parametrize("target", ["vacant", "hold", "reserved"])
async def test_occupied_plot_cannot_change_status_via_change_status_endpoint(
    client, db_session: AsyncSession, target: str,
):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = await _make_plot(db_session, account, status="occupied")

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


async def test_occupied_plot_cannot_be_reverted_to_vacant(client, db_session: AsyncSession):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = await _make_plot(db_session, account, status="occupied")

    resp = await client.patch(f"{PLOTS_URL}/{plot.id}/revert", headers=_headers(user, account))
    assert resp.status_code == 422, resp.text

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


async def test_occupied_plot_can_be_saved_with_same_status(client, db_session: AsyncSession):
    """A no-op save (status unchanged) must never be rejected as an illegal transition."""
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = await _make_plot(db_session, account, status="occupied")

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


# ── reserved can only step back to vacant, not hold ─────────────────────────

async def test_reserved_plot_cannot_move_to_hold(client, db_session: AsyncSession):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = await _make_plot(db_session, account, status="reserved")

    resp = await client.patch(
        f"{PLOTS_URL}/{plot.id}", json={"status": "hold"}, headers=_headers(user, account),
    )
    assert resp.status_code == 422, resp.text
    assert "hold" in resp.json()["message"].lower() or "vacant" in resp.json()["message"].lower()

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


async def test_reserved_plot_can_move_to_vacant(client, db_session: AsyncSession):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = await _make_plot(db_session, account, status="reserved")

    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_reserved_plot_can_move_forward_to_occupied(client, db_session: AsyncSession):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = await _make_plot(db_session, account, status="reserved")

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


async def test_reserved_plot_cannot_revert_to_hold_via_status_endpoint(
    client, db_session: AsyncSession,
):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = await _make_plot(db_session, account, status="reserved")

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


# ── vacant and hold are unrestricted ────────────────────────────────────────

@pytest.mark.parametrize("target", ["hold", "reserved", "occupied"])
async def test_vacant_plot_can_move_to_any_status(
    client, db_session: AsyncSession, target: str,
):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = await _make_plot(db_session, account, status="vacant")

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


@pytest.mark.parametrize("target", ["vacant", "reserved", "occupied"])
async def test_hold_plot_can_move_to_any_status(
    client, db_session: AsyncSession, target: str,
):
    account = await _make_account(db_session)
    user = await _make_user(db_session, account)
    plot = await _make_plot(db_session, account, status="hold")

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