"""Phase 12.3 Table Service & Reservations API / tenant / validation tests."""
from __future__ import annotations

from datetime import datetime, timedelta, timezone

import pytest

from app.events.publisher import get_event_publisher
from app.events.types import HospitalityEventType
from app.tests.conftest import TENANT_A, TENANT_B, tenant_headers


async def _seed_venue_table(client, tenant_id, *, venue_code: str = "v1", table_code: str = "t1"):
    h = tenant_headers(tenant_id)
    venue = await client.post(
        "/api/v1/venues",
        json={"code": venue_code, "name": "Cafe", "status": "active", "venue_format": "cafe"},
        headers=h,
    )
    assert venue.status_code == 201, venue.text
    venue_id = venue.json()["id"]

    area = await client.post(
        "/api/v1/dining-areas",
        json={"venue_id": venue_id, "code": f"{table_code}-area", "name": "Main Hall"},
        headers=h,
    )
    assert area.status_code == 201, area.text
    area_id = area.json()["id"]

    table = await client.post(
        "/api/v1/tables",
        json={
            "venue_id": venue_id,
            "dining_area_id": area_id,
            "code": table_code,
            "name": "Table",
            "capacity": 4,
        },
        headers=h,
    )
    assert table.status_code == 201, table.text
    return {
        "headers": h,
        "venue_id": venue_id,
        "dining_area_id": area_id,
        "table_id": table.json()["id"],
    }


def _window():
    start = datetime.now(timezone.utc) + timedelta(hours=1)
    end = start + timedelta(hours=2)
    return start.isoformat(), end.isoformat()


@pytest.mark.asyncio
async def test_reservation_lifecycle_and_events(client):
    seed = await _seed_venue_table(client, TENANT_A, venue_code="res-v1", table_code="res-t1")
    h = seed["headers"]
    reserved_from, reserved_to = _window()

    reservation = await client.post(
        "/api/v1/reservations",
        json={
            "venue_id": seed["venue_id"],
            "dining_area_id": seed["dining_area_id"],
            "table_id": seed["table_id"],
            "code": "res-1",
            "guest_name": "Ali",
            "guest_phone": "09120000000",
            "party_size": 2,
            "reserved_from": reserved_from,
            "reserved_to": reserved_to,
        },
        headers=h,
    )
    assert reservation.status_code == 201, reservation.text
    assert reservation.json()["status"] == "pending"
    reservation_id = reservation.json()["id"]

    confirmed = await client.post(
        f"/api/v1/reservations/{reservation_id}/status",
        json={"status": "confirmed"},
        headers=h,
    )
    assert confirmed.status_code == 200, confirmed.text
    assert confirmed.json()["status"] == "confirmed"

    listed = await client.get("/api/v1/reservations", headers=h)
    assert listed.status_code == 200
    assert len(listed.json()) >= 1

    published = {e.event_type for e in get_event_publisher().published}
    assert HospitalityEventType.RESERVATION_CREATED.value in published
    assert HospitalityEventType.RESERVATION_STATUS_CHANGED.value in published


@pytest.mark.asyncio
async def test_reservation_tenant_isolation(client):
    seed = await _seed_venue_table(client, TENANT_A, venue_code="iso-v1", table_code="iso-t1")
    reserved_from, reserved_to = _window()
    reservation = await client.post(
        "/api/v1/reservations",
        json={
            "venue_id": seed["venue_id"],
            "code": "iso-res",
            "guest_name": "Sara",
            "party_size": 3,
            "reserved_from": reserved_from,
            "reserved_to": reserved_to,
        },
        headers=seed["headers"],
    )
    assert reservation.status_code == 201, reservation.text
    reservation_id = reservation.json()["id"]

    denied = await client.get(
        f"/api/v1/reservations/{reservation_id}", headers=tenant_headers(TENANT_B)
    )
    assert denied.status_code == 404


@pytest.mark.asyncio
async def test_reservation_invalid_window_rejected(client):
    seed = await _seed_venue_table(client, TENANT_A, venue_code="win-v1", table_code="win-t1")
    start = datetime.now(timezone.utc) + timedelta(hours=2)
    end = start - timedelta(hours=1)
    bad = await client.post(
        "/api/v1/reservations",
        json={
            "venue_id": seed["venue_id"],
            "code": "bad-res",
            "guest_name": "Neda",
            "party_size": 2,
            "reserved_from": start.isoformat(),
            "reserved_to": end.isoformat(),
        },
        headers=seed["headers"],
    )
    assert bad.status_code == 422


@pytest.mark.asyncio
async def test_reservation_status_final_cannot_change(client):
    seed = await _seed_venue_table(client, TENANT_A, venue_code="fin-v1", table_code="fin-t1")
    h = seed["headers"]
    reserved_from, reserved_to = _window()
    reservation = await client.post(
        "/api/v1/reservations",
        json={
            "venue_id": seed["venue_id"],
            "code": "fin-res",
            "guest_name": "Reza",
            "party_size": 2,
            "reserved_from": reserved_from,
            "reserved_to": reserved_to,
        },
        headers=h,
    )
    assert reservation.status_code == 201, reservation.text
    reservation_id = reservation.json()["id"]

    cancelled = await client.post(
        f"/api/v1/reservations/{reservation_id}/status",
        json={"status": "cancelled"},
        headers=h,
    )
    assert cancelled.status_code == 200, cancelled.text

    blocked = await client.post(
        f"/api/v1/reservations/{reservation_id}/status",
        json={"status": "confirmed"},
        headers=h,
    )
    assert blocked.status_code == 409


@pytest.mark.asyncio
async def test_table_assignment_lifecycle_and_active_conflict(client):
    seed = await _seed_venue_table(client, TENANT_A, venue_code="ta-v1", table_code="ta-t1")
    h = seed["headers"]

    assignment = await client.post(
        "/api/v1/table-assignments",
        json={
            "venue_id": seed["venue_id"],
            "table_id": seed["table_id"],
            "guest_label": "Walk-in",
        },
        headers=h,
    )
    assert assignment.status_code == 201, assignment.text
    assert assignment.json()["status"] == "active"
    assignment_id = assignment.json()["id"]

    conflict = await client.post(
        "/api/v1/table-assignments",
        json={"venue_id": seed["venue_id"], "table_id": seed["table_id"]},
        headers=h,
    )
    assert conflict.status_code == 409

    released = await client.post(
        f"/api/v1/table-assignments/{assignment_id}/release", headers=h
    )
    assert released.status_code == 200, released.text
    assert released.json()["status"] == "released"

    again = await client.post(
        "/api/v1/table-assignments",
        json={"venue_id": seed["venue_id"], "table_id": seed["table_id"]},
        headers=h,
    )
    assert again.status_code == 201, again.text

    published = {e.event_type for e in get_event_publisher().published}
    assert HospitalityEventType.TABLE_ASSIGNMENT_CREATED.value in published


@pytest.mark.asyncio
async def test_service_request_lifecycle(client):
    seed = await _seed_venue_table(client, TENANT_A, venue_code="sr-v1", table_code="sr-t1")
    h = seed["headers"]

    request = await client.post(
        "/api/v1/service-requests",
        json={
            "venue_id": seed["venue_id"],
            "table_id": seed["table_id"],
            "code": "sr-1",
            "kind": "call_waiter",
        },
        headers=h,
    )
    assert request.status_code == 201, request.text
    assert request.json()["status"] == "open"
    request_id = request.json()["id"]

    acknowledged = await client.post(
        f"/api/v1/service-requests/{request_id}/status",
        json={"status": "acknowledged"},
        headers=h,
    )
    assert acknowledged.status_code == 200, acknowledged.text

    closed = await client.post(
        f"/api/v1/service-requests/{request_id}/status",
        json={"status": "closed"},
        headers=h,
    )
    assert closed.status_code == 200, closed.text

    blocked = await client.post(
        f"/api/v1/service-requests/{request_id}/status",
        json={"status": "open"},
        headers=h,
    )
    assert blocked.status_code == 409

    published = {e.event_type for e in get_event_publisher().published}
    assert HospitalityEventType.SERVICE_REQUEST_CREATED.value in published


@pytest.mark.asyncio
async def test_service_request_duplicate_code_rejected(client):
    seed = await _seed_venue_table(client, TENANT_A, venue_code="sr-dup-v1", table_code="sr-dup-t1")
    h = seed["headers"]
    body = {
        "venue_id": seed["venue_id"],
        "table_id": seed["table_id"],
        "code": "dup-sr",
        "kind": "water",
    }
    first = await client.post("/api/v1/service-requests", json=body, headers=h)
    assert first.status_code == 201, first.text
    second = await client.post("/api/v1/service-requests", json=body, headers=h)
    assert second.status_code == 409


@pytest.mark.asyncio
async def test_waitlist_entry_lifecycle(client):
    seed = await _seed_venue_table(client, TENANT_A, venue_code="wl-v1", table_code="wl-t1")
    h = seed["headers"]

    entry = await client.post(
        "/api/v1/waitlist",
        json={
            "venue_id": seed["venue_id"],
            "code": "wl-1",
            "guest_name": "Mina",
            "party_size": 4,
            "phone": "09121111111",
        },
        headers=h,
    )
    assert entry.status_code == 201, entry.text
    assert entry.json()["status"] == "waiting"
    entry_id = entry.json()["id"]

    seated = await client.post(
        f"/api/v1/waitlist/{entry_id}/status",
        json={"status": "seated"},
        headers=h,
    )
    assert seated.status_code == 200, seated.text
    assert seated.json()["status"] == "seated"

    blocked = await client.post(
        f"/api/v1/waitlist/{entry_id}/status",
        json={"status": "waiting"},
        headers=h,
    )
    assert blocked.status_code == 409

    published = {e.event_type for e in get_event_publisher().published}
    assert HospitalityEventType.WAITLIST_ENTRY_CREATED.value in published


@pytest.mark.asyncio
async def test_waitlist_entry_party_size_validation(client):
    seed = await _seed_venue_table(client, TENANT_A, venue_code="wl-val-v1", table_code="wl-val-t1")
    bad = await client.post(
        "/api/v1/waitlist",
        json={
            "venue_id": seed["venue_id"],
            "code": "wl-bad",
            "guest_name": "Kian",
            "party_size": 0,
        },
        headers=seed["headers"],
    )
    assert bad.status_code == 422


@pytest.mark.asyncio
async def test_capabilities_phase_12_3(client):
    resp = await client.get("/capabilities")
    assert resp.status_code == 200
    body = resp.json()
    assert body["features"]["table_service"] is True
    assert body["features"]["reservation"] is True
    assert body["features"]["pos_engine"] is False
