from __future__ import annotations

from pathlib import Path
from unittest import TestCase

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


class TestNotificationApiContracts(TestCase):
    def _read_notifications_router(self) -> str:
        path = _REPO_ROOT / "backend/app/routers/notifications.py"
        return path.read_text(encoding="utf-8")

    def _read_notifications_service(self) -> str:
        path = _REPO_ROOT / "backend/app/services/notifications.py"
        return path.read_text(encoding="utf-8")

    def _read_reservation_object(self) -> str:
        path = _REPO_ROOT / "restate_services/objects/reservation.py"
        return path.read_text(encoding="utf-8")

    def _read_order_object(self) -> str:
        path = _REPO_ROOT / "restate_services/objects/order.py"
        return path.read_text(encoding="utf-8")

    def test_list_endpoint_is_tenant_scoped(self) -> None:
        source = self._read_notifications_router()
        self.assertIn("Notification.restaurant_id == restaurant.id", source)

    def test_list_endpoint_orders_unread_first(self) -> None:
        source = self._read_notifications_router()
        self.assertIn("asc(is_read_col)", source)
        self.assertIn("desc(created_at_col)", source)

    def test_mark_read_endpoint_updates_single_notification(self) -> None:
        source = self._read_notifications_router()
        self.assertIn('@router.post("/{notification_id}/read"', source)
        self.assertIn("Notification.id == notification_id", source)
        self.assertIn("notification.is_read = True", source)

    def test_mark_all_read_endpoint_exists(self) -> None:
        source = self._read_notifications_router()
        self.assertIn('@router.post("/read-all")', source)
        self.assertIn("update(Notification)", source)
        self.assertIn("is_read_col.is_(False)", source)

    def test_unread_count_endpoint_uses_count_query(self) -> None:
        source = self._read_notifications_router()
        self.assertIn('@router.get("/unread-count"', source)
        self.assertIn("func.count()", source)

    def test_notification_service_has_preference_guard(self) -> None:
        source = self._read_notifications_service()
        self.assertIn("get_notification_preferences", source)
        self.assertIn("in_app_enabled", source)
        self.assertIn("events.get(type, True)", source)

    def test_notification_service_has_idempotency_guard(self) -> None:
        source = self._read_notifications_service()
        self.assertIn('metadata_col["idempotency_key"]', source)
        self.assertIn("idempotency_key", source)
        self.assertIn("return existing", source)

    def test_restate_reservation_emits_notifications(self) -> None:
        source = self._read_reservation_object()
        self.assertIn("emit_reservation_created_notification", source)
        self.assertIn("emit_reservation_cancelled_notification", source)
        self.assertIn("emit_reservation_approved_notification", source)

    def test_restate_order_emits_notifications(self) -> None:
        source = self._read_order_object()
        self.assertIn("emit_order_created_notification", source)
        self.assertIn("emit_order_status_notification", source)
        self.assertIn("emit_order_cancelled_notification", source)


class TestNotificationEventTypeConsistency(TestCase):
    """Event types in preferences, settings UI, and Restate emission must align."""

    def _preference_event_keys(self) -> set[str]:
        from app.models.restaurant import DEFAULT_NOTIFICATION_PREFERENCES

        return set(DEFAULT_NOTIFICATION_PREFERENCES["events"].keys())

    def _emitted_event_types(self) -> set[str]:
        """Collect all event_type strings from Restate notification emissions."""
        import re

        types: set[str] = set()
        for path in [
            _REPO_ROOT / "restate_services/objects/reservation.py",
            _REPO_ROOT / "restate_services/objects/order.py",
        ]:
            source = path.read_text(encoding="utf-8")
            # Match event_type="..." in _create_in_app_notification calls
            types.update(re.findall(r'event_type="([^"]+)"', source))
        return types

    def test_all_emitted_types_have_preference_keys(self) -> None:
        """Every event type emitted by Restate must appear in preferences."""
        emitted = self._emitted_event_types()
        prefs = self._preference_event_keys()
        missing = emitted - prefs
        self.assertEqual(
            missing,
            set(),
            f"Emitted event types missing from preferences: {missing}",
        )

    def test_all_preference_keys_are_emitted(self) -> None:
        """Every preference key should correspond to an emitted event type."""
        emitted = self._emitted_event_types()
        prefs = self._preference_event_keys()
        unused = prefs - emitted
        self.assertEqual(
            unused,
            set(),
            f"Preference keys with no emitter: {unused}",
        )

    def test_order_cancel_uses_order_cancelled_event_type(self) -> None:
        """Order cancel notification must use 'order_cancelled' not 'order_status_changed'."""
        source = (_REPO_ROOT / "restate_services/objects/order.py").read_text(encoding="utf-8")
        # Find the cancel handler's notification emission
        # The cancel handler is after the 'cancel_order' ctx.run call
        cancel_section = source.split('"cancel_order"')[1]
        notification_section = cancel_section.split("emit_order_cancelled_notification")[0]
        self.assertIn('event_type="order_cancelled"', notification_section)
