"""Tests for the public booking endpoint (create_public_reservation).

Guards against:
1. Response model constructed without fields computed by the Restate workflow
   (end_time, snapped reserved_at, table assignment).
2. The endpoint must fetch the committed DB row — not hand-build the response
   from request data alone — because the workflow computes fields the caller
   never provided.
"""

from __future__ import annotations

import os
from datetime import datetime, timedelta
from pathlib import Path
from unittest import TestCase

from pydantic import ValidationError

_ = os.environ.setdefault("NEON_DATABASE_URL", "postgresql://user:pass@localhost:5432/testdb")

_REPO_ROOT = Path(__file__).resolve().parents[2]


class TestPublicBookingResponseModel(TestCase):
    """ReservationRead must include workflow-computed fields."""

    def test_reservation_read_requires_end_time(self) -> None:
        """ReservationRead inherits end_time as required from ReservationBase.

        If someone constructs ReservationRead with only the fields available
        from PublicReservationCreate (no end_time), it must fail — proving the
        endpoint cannot hand-build the response from request data alone.
        """
        from app.models.reservation import ReservationRead

        with self.assertRaises(ValidationError) as cm:
            ReservationRead(  # type: ignore[call-arg]
                id="test-id",
                restaurant_id="test-restaurant",
                guest_name="Alice",
                guest_email="alice@example.com",
                guest_phone="+32 470 000 000",
                party_size=2,
                reserved_at=datetime(2026, 3, 15, 12, 0),
                notes=None,
                status="pending",
                source="widget",
                # end_time intentionally omitted
            )

        errors = cm.exception.errors()
        missing_fields = {e["loc"][0] for e in errors if e["type"] == "missing"}
        self.assertIn("end_time", missing_fields)

    def test_reservation_read_accepts_complete_fields(self) -> None:
        """ReservationRead succeeds when end_time is provided."""
        from app.models.reservation import ReservationRead

        reserved_at = datetime(2026, 3, 15, 12, 0)
        obj = ReservationRead(
            id="test-id",
            restaurant_id="test-restaurant",
            guest_name="Alice",
            guest_email="alice@example.com",
            guest_phone="+32 470 000 000",
            party_size=2,
            reserved_at=reserved_at,
            end_time=reserved_at + timedelta(minutes=90),
            notes=None,
            status="pending",
            source="widget",
        )
        self.assertEqual(obj.end_time, reserved_at + timedelta(minutes=90))


class TestPublicBookingEndpointContract(TestCase):
    """Structural contracts for create_public_reservation in public.py."""

    def _read_source(self) -> str:
        path = _REPO_ROOT / "backend/app/routers/public.py"
        return path.read_text(encoding="utf-8")

    def _get_endpoint_block(self) -> str:
        source = self._read_source()
        idx = source.index("async def create_public_reservation")
        # Grab until the next top-level def/class or end of file
        rest = source[idx:]
        next_fn = rest.find("\nasync def ", 10)
        if next_fn < 0:
            next_fn = rest.find("\ndef ", 10)
        if next_fn < 0:
            next_fn = rest.find("\nclass ", 10)
        return rest[:next_fn] if next_fn > 0 else rest

    def test_endpoint_exists(self) -> None:
        source = self._read_source()
        self.assertIn("async def create_public_reservation", source)
        self.assertIn('"/restaurants/{slug}/reservations"', source)

    def test_endpoint_returns_201(self) -> None:
        source = self._read_source()
        # Find the decorator block near the endpoint
        idx = source.index("async def create_public_reservation")
        decorator_block = source[max(0, idx - 200) : idx]
        self.assertIn("HTTP_201_CREATED", decorator_block)

    def test_endpoint_fetches_committed_row_from_db(self) -> None:
        """The endpoint MUST read the reservation back from the DB after the
        Restate workflow completes.  Hand-building ReservationRead from request
        data misses workflow-computed fields (end_time, snapped reserved_at,
        table_id, combination_id).
        """
        block = self._get_endpoint_block()
        self.assertIn("select(Reservation)", block)
        self.assertIn("scalar_one_or_none", block)

    def test_endpoint_does_not_hand_build_reservation_read(self) -> None:
        """The response must come from model_validate, not a manual constructor
        with cherry-picked fields from the request body.
        """
        block = self._get_endpoint_block()
        self.assertIn("model_validate", block)

    def test_endpoint_calls_restate_workflow(self) -> None:
        block = self._get_endpoint_block()
        self.assertIn("ReservationWorkflow", block)
        self.assertIn("create_reservation", block)

    def test_endpoint_sets_widget_source(self) -> None:
        block = self._get_endpoint_block()
        self.assertIn('"source": "widget"', block)
