"""
Tests for INDL-57 — Support Tickets: site-admin cross-tenant queue.

Covers `/api/site-admin/support/tickets` (GET list w/ skip,limit,plan,status,q),
`/api/site-admin/support/tickets/{id}` (GET detail), `.../messages` (POST reply),
`.../status` (PATCH).

Backend-testable Acceptance Criteria covered here:
  AC-16 — table columns across every tenant, newest first, paginated
  AC-17 — search by organization name / ticket number / subject
  AC-18 — Plan filter (Starter / Professional / Enterprise)
  AC-19 — Status filter (Open / In Progress / Resolved / Closed)
  AC-20 — Ticket Detail: thread + organization/plan info + status field
  AC-21 — site-admin reply visible to the tenant on their own portal view
  AC-22 — status change persists and reflects in both surfaces
  AC-23 — only site_admin can access these endpoints (see also test_support_security.py)

Skipped (frontend-only, no backend-testable component):
  AC-14 — sidebar nav item visibility
  AC-15 — page header copy
"""
from __future__ import annotations

import uuid

import pytest
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.auth.models.user import User
from src.apps.tenants.models.account import Account
from src.core.constants import TicketStatus
from src.core.security import build_token_payload, create_access_token, hash_password

TENANT_URL = "/api/v1/support/tickets"
ADMIN_URL = "/api/site-admin/support/tickets"


# ── Helpers ───────────────────────────────────────────────────────────────────


async def _create_account(db: AsyncSession, *, org: str = "Test Cemetery", plan: str = "starter") -> Account:
    subdomain = f"cem-{uuid.uuid4().hex[:10]}"
    account = Account(
        organization_name=org,
        subdomain=subdomain,
        contact_email=f"contact@{subdomain}.ca",
        plan=plan,
        status="active",
    )
    db.add(account)
    await db.flush()
    return account


async def _create_user(db: AsyncSession, account: Account | None, *, role: str = "view_only", email: str | None = None) -> User:
    email = email or f"{role}-{uuid.uuid4().hex[:8]}@test.ca"
    user = User(
        tenant_id=account.id if account else None,
        email=email,
        password_hash=hash_password("Password123!"),
        first_name="Test",
        last_name=role.title(),
        role=role,
        status="active",
    )
    db.add(user)
    await db.flush()
    return user


def _headers(user: User, account: Account | None = None) -> dict:
    token = create_access_token(build_token_payload(user, account))
    return {"Authorization": f"Bearer {token}"}


async def _make_tenant(db: AsyncSession, *, role: str = "view_only", plan: str = "starter", org: str = "Test Cemetery"):
    account = await _create_account(db, org=org, plan=plan)
    user = await _create_user(db, account, role=role)
    return _headers(user, account), user, account


async def _site_admin_headers(db: AsyncSession) -> dict:
    user = await _create_user(db, None, role="site_admin", email=f"sa-{uuid.uuid4().hex[:8]}@indelis.com")
    return _headers(user)


async def _create_ticket(client: AsyncClient, tenant_headers: dict, *, subject: str = "Test issue", description: str = "Body text.") -> dict:
    resp = await client.post(TENANT_URL, json={"subject": subject, "description": description}, headers=tenant_headers)
    assert resp.status_code == 201, resp.text
    return resp.json()["data"]


# ── AC-16: List across every tenant, newest first, columns ──────────────────


@pytest.mark.asyncio
async def test_list_spans_multiple_tenants(client: AsyncClient, db_session: AsyncSession):
    """AC-16: The queue shows tickets from every organization, not just one."""
    sa_headers = await _site_admin_headers(db_session)
    headers_a, _, account_a = await _make_tenant(db_session, org="Green Hills Cemetery")
    headers_b, _, account_b = await _make_tenant(db_session, org="Red Hills Cemetery")

    await _create_ticket(client, headers_a, subject="Green Hills issue")
    await _create_ticket(client, headers_b, subject="Red Hills issue")

    resp = await client.get(ADMIN_URL, headers=sa_headers)
    assert resp.status_code == 200
    subjects = {item["subject"] for item in resp.json()["data"]}
    assert {"Green Hills issue", "Red Hills issue"}.issubset(subjects)


@pytest.mark.asyncio
async def test_list_newest_first(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    await _create_ticket(client, headers, subject="Oldest")
    await _create_ticket(client, headers, subject="Newest")

    resp = await client.get(ADMIN_URL, headers=sa_headers)
    subjects = [item["subject"] for item in resp.json()["data"]]
    assert subjects.index("Newest") < subjects.index("Oldest")


@pytest.mark.asyncio
async def test_list_item_shape_has_required_columns(client: AsyncClient, db_session: AsyncSession):
    """AC-16: Ticket #, Subject, Organization, Submitted By, Plan, Status, Date."""
    sa_headers = await _site_admin_headers(db_session)
    headers, user, account = await _make_tenant(db_session, org="Column Check Cemetery", plan="professional")
    await _create_ticket(client, headers, subject="Column check")

    resp = await client.get(f"{ADMIN_URL}?q=Column+check", headers=sa_headers)
    item = resp.json()["data"][0]
    assert item["ticket_number"].startswith("SUP-")
    assert item["subject"] == "Column check"
    assert item["organization_name"] == "Column Check Cemetery"
    assert item["submitted_by"] == f"Test {user.role.title()}"
    assert item["plan"] == "professional"
    assert item["status"] == "open"
    assert "created_at" in item


@pytest.mark.asyncio
async def test_list_default_page_size_is_20(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    resp = await client.get(ADMIN_URL, headers=sa_headers)
    assert resp.json()["page_size"] == 20


@pytest.mark.asyncio
async def test_list_pagination_skip_limit(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    for i in range(5):
        await _create_ticket(client, headers, subject=f"Page test {i}")

    resp = await client.get(f"{ADMIN_URL}?skip=0&limit=2", headers=sa_headers)
    body = resp.json()
    assert len(body["data"]) == 2
    assert body["total"] >= 5

    resp2 = await client.get(f"{ADMIN_URL}?skip=2&limit=2", headers=sa_headers)
    assert len(resp2.json()["data"]) == 2


@pytest.mark.asyncio
async def test_list_limit_upper_boundary_rejected(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    resp = await client.get(f"{ADMIN_URL}?limit=101", headers=sa_headers)
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_list_limit_zero_rejected(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    resp = await client.get(f"{ADMIN_URL}?limit=0", headers=sa_headers)
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_list_skip_negative_rejected(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    resp = await client.get(f"{ADMIN_URL}?skip=-1", headers=sa_headers)
    assert resp.status_code == 422


# ── AC-18: Plan filter ────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_filter_by_plan_professional(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers_starter, _, _ = await _make_tenant(db_session, plan="starter", org="Starter Org")
    headers_pro, _, _ = await _make_tenant(db_session, plan="professional", org="Pro Org")
    await _create_ticket(client, headers_starter, subject="Starter ticket")
    await _create_ticket(client, headers_pro, subject="Pro ticket")

    resp = await client.get(f"{ADMIN_URL}?plan=professional", headers=sa_headers)
    items = resp.json()["data"]
    assert all(item["plan"] == "professional" for item in items)
    assert any(item["subject"] == "Pro ticket" for item in items)
    assert not any(item["subject"] == "Starter ticket" for item in items)


@pytest.mark.asyncio
async def test_filter_by_plan_enterprise(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers_ent, _, _ = await _make_tenant(db_session, plan="enterprise", org="Enterprise Org")
    await _create_ticket(client, headers_ent, subject="Enterprise ticket")

    resp = await client.get(f"{ADMIN_URL}?plan=enterprise", headers=sa_headers)
    items = resp.json()["data"]
    assert any(item["subject"] == "Enterprise ticket" for item in items)
    assert all(item["plan"] == "enterprise" for item in items)


@pytest.mark.asyncio
async def test_filter_by_plan_starter(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers_starter, _, _ = await _make_tenant(db_session, plan="starter", org="Starter Only Org")
    headers_ent, _, _ = await _make_tenant(db_session, plan="enterprise", org="Enterprise Only Org")
    await _create_ticket(client, headers_starter, subject="Starter only ticket")
    await _create_ticket(client, headers_ent, subject="Enterprise only ticket")

    resp = await client.get(f"{ADMIN_URL}?plan=starter", headers=sa_headers)
    items = resp.json()["data"]
    assert any(item["subject"] == "Starter only ticket" for item in items)
    assert not any(item["subject"] == "Enterprise only ticket" for item in items)


# ── AC-19: Status filter ──────────────────────────────────────────────────────


@pytest.mark.parametrize("status_value", ["open", "in_progress", "resolved", "closed"])
@pytest.mark.asyncio
async def test_filter_by_status_each_value(client: AsyncClient, db_session: AsyncSession, status_value: str):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers, subject=f"Status {status_value} ticket")

    if status_value != "open":
        patch_resp = await client.patch(
            f"{ADMIN_URL}/{ticket['id']}/status", json={"status": status_value}, headers=sa_headers
        )
        assert patch_resp.status_code == 200

    resp = await client.get(f"{ADMIN_URL}?status={status_value}", headers=sa_headers)
    items = resp.json()["data"]
    assert any(item["id"] == ticket["id"] for item in items)
    assert all(item["status"] == status_value for item in items)


# ── AC-17: Search ─────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_search_by_organization_name(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session, org="Uniquely Named Cemetery")
    await _create_ticket(client, headers, subject="Something unrelated")

    resp = await client.get(f"{ADMIN_URL}?q=Uniquely+Named", headers=sa_headers)
    items = resp.json()["data"]
    assert len(items) >= 1
    assert all("Uniquely Named" in item["organization_name"] for item in items)


@pytest.mark.asyncio
async def test_search_by_ticket_number(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers, subject="Number search target")

    resp = await client.get(f"{ADMIN_URL}?q={ticket['ticket_number']}", headers=sa_headers)
    items = resp.json()["data"]
    assert any(item["id"] == ticket["id"] for item in items)


@pytest.mark.asyncio
async def test_search_by_subject(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    await _create_ticket(client, headers, subject="Very distinctive subject phrase")

    resp = await client.get(f"{ADMIN_URL}?q=distinctive+subject", headers=sa_headers)
    items = resp.json()["data"]
    assert any("distinctive subject" in item["subject"] for item in items)


@pytest.mark.asyncio
async def test_search_no_match_returns_empty(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    resp = await client.get(f"{ADMIN_URL}?q=zzz-no-such-match-zzz", headers=sa_headers)
    body = resp.json()
    assert body["data"] == []
    assert body["total"] == 0


@pytest.mark.asyncio
async def test_combined_plan_status_and_search_filters(client: AsyncClient, db_session: AsyncSession):
    """Filters compose with AND logic."""
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session, plan="professional", org="Combined Filter Org")
    ticket = await _create_ticket(client, headers, subject="Combined filter subject")
    await client.patch(f"{ADMIN_URL}/{ticket['id']}/status", json={"status": "in_progress"}, headers=sa_headers)

    resp = await client.get(
        f"{ADMIN_URL}?plan=professional&status=in_progress&q=Combined+filter",
        headers=sa_headers,
    )
    items = resp.json()["data"]
    assert len(items) == 1
    assert items[0]["id"] == ticket["id"]

    # A non-matching plan combined with the same q/status should exclude it.
    resp2 = await client.get(
        f"{ADMIN_URL}?plan=enterprise&status=in_progress&q=Combined+filter",
        headers=sa_headers,
    )
    assert not any(item["id"] == ticket["id"] for item in resp2.json()["data"])


# ── AC-20: Ticket detail (any tenant) ────────────────────────────────────────


@pytest.mark.asyncio
async def test_get_ticket_detail_includes_org_and_plan(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session, plan="enterprise", org="Detail Check Cemetery")
    ticket = await _create_ticket(client, headers, subject="Detail check")

    resp = await client.get(f"{ADMIN_URL}/{ticket['id']}", headers=sa_headers)
    assert resp.status_code == 200
    data = resp.json()["data"]
    assert data["organization_name"] == "Detail Check Cemetery"
    assert data["plan"] == "enterprise"
    assert data["status"] == "open"
    assert len(data["messages"]) == 1


@pytest.mark.asyncio
async def test_get_ticket_detail_nonexistent_returns_404(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    resp = await client.get(f"{ADMIN_URL}/{uuid.uuid4()}", headers=sa_headers)
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_get_ticket_detail_malformed_uuid_returns_422(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    resp = await client.get(f"{ADMIN_URL}/not-a-uuid", headers=sa_headers)
    assert resp.status_code == 422


# ── AC-21: Site-admin reply visible to tenant ────────────────────────────────


@pytest.mark.asyncio
async def test_site_admin_reply_visible_in_own_thread(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers, subject="Reply visibility")

    reply_resp = await client.post(
        f"{ADMIN_URL}/{ticket['id']}/messages", json={"body": "Thanks for reaching out."}, headers=sa_headers
    )
    assert reply_resp.status_code == 201
    reply_data = reply_resp.json()["data"]
    assert reply_data["author_role_snapshot"] == "site_admin"
    assert reply_data["author_name"] == "INDELIS Support"


@pytest.mark.asyncio
async def test_site_admin_reply_visible_on_tenant_portal_view(client: AsyncClient, db_session: AsyncSession):
    """AC-21: The reply is visible to the tenant on their own portal view."""
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers, subject="Cross-surface visibility")

    await client.post(
        f"{ADMIN_URL}/{ticket['id']}/messages", json={"body": "Can you confirm your browser?"}, headers=sa_headers
    )

    tenant_view = await client.get(f"{TENANT_URL}/{ticket['id']}", headers=headers)
    assert tenant_view.status_code == 200
    messages = tenant_view.json()["data"]["messages"]
    assert any(m["body"] == "Can you confirm your browser?" and m["author_name"] == "INDELIS Support" for m in messages)


@pytest.mark.asyncio
async def test_site_admin_reply_visible_on_site_admin_own_detail_view(client: AsyncClient, db_session: AsyncSession):
    """The site-admin's own GET detail view also renders 'INDELIS Support'
    for a site_admin-authored message (SiteAdminSupportService._serialize_message)."""
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers, subject="Site admin self-view")

    await client.post(f"{ADMIN_URL}/{ticket['id']}/messages", json={"body": "Reply from support."}, headers=sa_headers)

    detail = await client.get(f"{ADMIN_URL}/{ticket['id']}", headers=sa_headers)
    messages = detail.json()["data"]["messages"]
    reply = next(m for m in messages if m["body"] == "Reply from support.")
    assert reply["author_role_snapshot"] == "site_admin"
    assert reply["author_name"] == "INDELIS Support"


@pytest.mark.asyncio
async def test_site_admin_reply_bumps_last_message_at(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers, subject="Bump check")

    detail_before = (await client.get(f"{ADMIN_URL}/{ticket['id']}", headers=sa_headers)).json()["data"]

    await client.post(f"{ADMIN_URL}/{ticket['id']}/messages", json={"body": "Following up."}, headers=sa_headers)

    tenant_list = await client.get(TENANT_URL, headers=headers)
    item = next(i for i in tenant_list.json()["data"] if i["id"] == ticket["id"])
    assert item["last_message_at"] >= detail_before["created_at"]


@pytest.mark.asyncio
async def test_site_admin_reply_blank_body_returns_422(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers)

    resp = await client.post(f"{ADMIN_URL}/{ticket['id']}/messages", json={"body": "   "}, headers=sa_headers)
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_site_admin_reply_oversized_body_returns_422(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers)

    resp = await client.post(f"{ADMIN_URL}/{ticket['id']}/messages", json={"body": "Z" * 5001}, headers=sa_headers)
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_site_admin_reply_nonexistent_ticket_returns_404(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    resp = await client.post(f"{ADMIN_URL}/{uuid.uuid4()}/messages", json={"body": "Hello"}, headers=sa_headers)
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_site_admin_reply_rejects_extra_fields(client: AsyncClient, db_session: AsyncSession):
    """S-03: SiteAdminSupportReplyCreate exposes only 'body'."""
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers)

    resp = await client.post(
        f"{ADMIN_URL}/{ticket['id']}/messages",
        json={"body": "Hello", "author_user_id": str(uuid.uuid4())},
        headers=sa_headers,
    )
    assert resp.status_code == 422


# ── AC-22 / S-04: Status transitions ─────────────────────────────────────────


@pytest.mark.parametrize("status_value", [s.value for s in TicketStatus])
@pytest.mark.asyncio
async def test_status_update_accepts_all_enum_values(client: AsyncClient, db_session: AsyncSession, status_value: str):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers)

    resp = await client.patch(f"{ADMIN_URL}/{ticket['id']}/status", json={"status": status_value}, headers=sa_headers)
    assert resp.status_code == 200
    assert resp.json()["data"]["status"] == status_value


@pytest.mark.asyncio
async def test_status_update_persists_and_reflects_in_both_surfaces(client: AsyncClient, db_session: AsyncSession):
    """AC-22: Status change persists immediately and reflects in both the
    site-admin table/detail view and the tenant's own portal view."""
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers, subject="Status propagation")

    patch_resp = await client.patch(
        f"{ADMIN_URL}/{ticket['id']}/status", json={"status": "resolved"}, headers=sa_headers
    )
    assert patch_resp.status_code == 200

    admin_detail = await client.get(f"{ADMIN_URL}/{ticket['id']}", headers=sa_headers)
    assert admin_detail.json()["data"]["status"] == "resolved"

    admin_list = await client.get(f"{ADMIN_URL}?q=Status+propagation", headers=sa_headers)
    assert admin_list.json()["data"][0]["status"] == "resolved"

    tenant_detail = await client.get(f"{TENANT_URL}/{ticket['id']}", headers=headers)
    assert tenant_detail.json()["data"]["status"] == "resolved"

    tenant_list = await client.get(TENANT_URL, headers=headers)
    tenant_item = next(i for i in tenant_list.json()["data"] if i["id"] == ticket["id"])
    assert tenant_item["status"] == "resolved"


@pytest.mark.asyncio
async def test_status_update_invalid_value_returns_422(client: AsyncClient, db_session: AsyncSession):
    """S-04: An unrecognized status value fails Pydantic enum validation (422)."""
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers)

    resp = await client.patch(
        f"{ADMIN_URL}/{ticket['id']}/status", json={"status": "archived"}, headers=sa_headers
    )
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_status_update_nonexistent_ticket_returns_404(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    resp = await client.patch(f"{ADMIN_URL}/{uuid.uuid4()}/status", json={"status": "open"}, headers=sa_headers)
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_status_update_malformed_uuid_returns_422(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    resp = await client.patch(f"{ADMIN_URL}/not-a-uuid/status", json={"status": "open"}, headers=sa_headers)
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_status_update_rejects_extra_fields(client: AsyncClient, db_session: AsyncSession):
    """S-03: SiteAdminSupportStatusUpdate exposes only 'status'."""
    sa_headers = await _site_admin_headers(db_session)
    headers, _, tenant_account = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers)

    resp = await client.patch(
        f"{ADMIN_URL}/{ticket['id']}/status",
        json={"status": "open", "tenant_id": str(uuid.uuid4())},
        headers=sa_headers,
    )
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_status_update_missing_status_returns_422(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers)

    resp = await client.patch(f"{ADMIN_URL}/{ticket['id']}/status", json={}, headers=sa_headers)
    assert resp.status_code == 422


# ── Closed ticket freeze ─────────────────────────────────────────────────────
# Once a ticket's status is 'closed', it is a terminal state: no further
# status transitions are permitted (not even re-closing it), and no more
# replies can be posted by either the tenant or site_admin.


@pytest.mark.asyncio
async def test_status_update_rejected_once_ticket_is_closed(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers)

    close_resp = await client.patch(f"{ADMIN_URL}/{ticket['id']}/status", json={"status": "closed"}, headers=sa_headers)
    assert close_resp.status_code == 200
    assert close_resp.json()["data"]["status"] == "closed"

    reopen_resp = await client.patch(f"{ADMIN_URL}/{ticket['id']}/status", json={"status": "open"}, headers=sa_headers)
    assert reopen_resp.status_code == 409

    reclose_resp = await client.patch(f"{ADMIN_URL}/{ticket['id']}/status", json={"status": "closed"}, headers=sa_headers)
    assert reclose_resp.status_code == 409

    detail = await client.get(f"{ADMIN_URL}/{ticket['id']}", headers=sa_headers)
    assert detail.json()["data"]["status"] == "closed"


@pytest.mark.asyncio
async def test_site_admin_reply_rejected_once_ticket_is_closed(client: AsyncClient, db_session: AsyncSession):
    sa_headers = await _site_admin_headers(db_session)
    headers, _, _ = await _make_tenant(db_session)
    ticket = await _create_ticket(client, headers)

    await client.patch(f"{ADMIN_URL}/{ticket['id']}/status", json={"status": "closed"}, headers=sa_headers)

    resp = await client.post(f"{ADMIN_URL}/{ticket['id']}/messages", json={"body": "Reopening?"}, headers=sa_headers)
    assert resp.status_code == 409

    detail = await client.get(f"{ADMIN_URL}/{ticket['id']}", headers=sa_headers)
    assert len(detail.json()["data"]["messages"]) == 1
