"""PATCH /plots/{id} immutability guard — Section, Plot ID, Plot type, and
GPS coordinates become permanent once a plot exists (referenced by records/
sales/contracts). Only Status & notes and Display label stay editable after
creation. Enforced server-side (422) so the rule can't be bypassed via a
direct API call, in addition to the map Edit Plot form disabling those
fields client-side.
"""
from uuid import uuid4

import pytest
from httpx import AsyncClient
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.plots.models.plot_type import PlotType
from src.apps.sections.models.section import Section
from src.apps.tenants.models.account import Account
from src.core.security import build_token_payload, create_access_token

pytestmark = pytest.mark.asyncio


async def _make_account(db: AsyncSession) -> Account:
    uid = uuid4().hex[:8]
    acc = Account(
        organization_name=f"Immutable {uid}",
        subdomain=f"immutable-{uid}",
        contact_email=f"immutable-{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") -> str:
    user = User(
        tenant_id=account.id,
        email=f"{role}-{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 create_access_token(build_token_payload(user, account))


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


async def _make_section(db, account, code="A", name="Section A") -> Section:
    sec = Section(
        tenant_id=account.id, code=code, name=name,
        plot_number_prefix=code, next_plot_seq=1,
    )
    db.add(sec)
    await db.flush()
    return sec


async def _make_plot_type(db, account, sections=None) -> PlotType:
    pt = PlotType(
        tenant_id=account.id, name=f"Standard {uuid4().hex[:4]}",
        default_width_m=1.2, default_length_m=2.4, default_depth_m=1.8,
        default_gap_m=0.3, default_price=4200, sections=sections or [],
    )
    db.add(pt)
    await db.flush()
    return pt


async def _make_plot(db, account, section, plot_type) -> Plot:
    plot = Plot(
        tenant_id=account.id, plot_ref="A-1", section_id=section.id,
        plot_type_id=plot_type.id, status="vacant", price_override=100,
        latitude=45.4215, longitude=-75.6972,
    )
    db.add(plot)
    await db.flush()
    return plot


@pytest.mark.parametrize("field,value_fn", [
    ("plot_ref", lambda ctx: "A-99"),
    ("section_id", lambda ctx: str(ctx["other_section"].id)),
    ("plot_type_id", lambda ctx: str(ctx["other_plot_type"].id)),
    ("latitude", lambda ctx: 45.5),
    ("longitude", lambda ctx: -75.5),
])
async def test_update_rejects_locked_field(client: AsyncClient, db_session, field, value_fn):
    acc = await _make_account(db_session)
    token = await _make_user(db_session, acc)
    sec = await _make_section(db_session, acc)
    other_section = await _make_section(db_session, acc, code="B", name="Section B")
    pt = await _make_plot_type(db_session, acc, sections=[sec, other_section])
    other_pt = await _make_plot_type(db_session, acc, sections=[sec, other_section])
    plot = await _make_plot(db_session, acc, sec, pt)

    ctx = {"other_section": other_section, "other_plot_type": other_pt}
    resp = await client.patch(
        f"/api/v1/plots/{plot.id}",
        json={field: value_fn(ctx)},
        headers=_headers(token, acc),
    )
    assert resp.status_code == 422, resp.text
    assert field in resp.json()["message"].lower()


@pytest.mark.parametrize("field,value", [
    ("status", "reserved"),
    ("price_override", 999.99),
    ("notes", "internal note"),
    ("public_description", "a lovely spot"),
    ("label_manual", "Rose Garden 12"),
])
async def test_update_allows_editable_field(client: AsyncClient, db_session, field, value):
    acc = await _make_account(db_session)
    token = await _make_user(db_session, acc)
    sec = await _make_section(db_session, acc)
    pt = await _make_plot_type(db_session, acc, sections=[sec])
    plot = await _make_plot(db_session, acc, sec, pt)

    resp = await client.patch(
        f"/api/v1/plots/{plot.id}",
        json={field: value},
        headers=_headers(token, acc),
    )
    assert resp.status_code == 200, resp.text
    assert resp.json()["data"][field] == value


async def test_update_geometry_still_recomputes_lat_lng(client: AsyncClient, db_session):
    """The immutability guard blocks client-supplied latitude/longitude, but
    must not break the geometry write path — the server still derives and
    stores lat/lng itself from the polygon centroid."""
    acc = await _make_account(db_session)
    token = await _make_user(db_session, acc)
    sec = await _make_section(db_session, acc)
    pt = await _make_plot_type(db_session, acc, sections=[sec])
    plot = await _make_plot(db_session, acc, sec, pt)

    geometry = {
        "type": "Polygon",
        "coordinates": [[
            [-75.6972, 45.4215], [-75.6970, 45.4215],
            [-75.6970, 45.4217], [-75.6972, 45.4217], [-75.6972, 45.4215],
        ]],
    }
    resp = await client.patch(
        f"/api/v1/plots/{plot.id}",
        json={"geometry": geometry},
        headers=_headers(token, acc),
    )
    assert resp.status_code == 200, resp.text
    data = resp.json()["data"]
    assert data["latitude"] is not None
    assert data["longitude"] is not None


async def test_update_cross_tenant_plot_404s_before_immutability_check(client: AsyncClient, db_session):
    """A foreign tenant's plot id must 404 (SEC-02 tenant isolation) rather
    than 422 from the immutability guard — the guard runs after the
    tenant-scoped lookup, not before it."""
    acc_a = await _make_account(db_session)
    sec_a = await _make_section(db_session, acc_a)
    pt_a = await _make_plot_type(db_session, acc_a, sections=[sec_a])
    plot = await _make_plot(db_session, acc_a, sec_a, pt_a)

    acc_b = await _make_account(db_session)
    token_b = await _make_user(db_session, acc_b)

    resp = await client.patch(
        f"/api/v1/plots/{plot.id}",
        json={"plot_ref": "B-1"},
        headers=_headers(token_b, acc_b),
    )
    assert resp.status_code == 404
