"""Tests for the dish chooser feature."""

import os
from unittest import TestCase

from pydantic import ValidationError

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


class TestIsDishChooserRequired(TestCase):
    def test_returns_true_for_gated_source_at_threshold(self) -> None:
        from app.services.dish_chooser import is_dish_chooser_required

        self.assertTrue(
            is_dish_chooser_required(
                {"dish_chooser_enabled": True, "dish_chooser_min_party_size": 8},
                party_size=8,
                source="widget",
            )
        )

    def test_returns_false_below_threshold(self) -> None:
        from app.services.dish_chooser import is_dish_chooser_required

        self.assertFalse(
            is_dish_chooser_required(
                {"dish_chooser_enabled": True, "dish_chooser_min_party_size": 8},
                party_size=7,
                source="widget",
            )
        )

    def test_returns_false_when_disabled(self) -> None:
        from app.services.dish_chooser import is_dish_chooser_required

        self.assertFalse(
            is_dish_chooser_required(
                {"dish_chooser_enabled": False, "dish_chooser_min_party_size": 8},
                party_size=10,
                source="widget",
            )
        )

    def test_returns_false_for_manual_source(self) -> None:
        from app.services.dish_chooser import is_dish_chooser_required

        self.assertFalse(
            is_dish_chooser_required(
                {"dish_chooser_enabled": True, "dish_chooser_min_party_size": 8},
                party_size=10,
                source="manual",
            )
        )

    def test_returns_true_for_agent_source(self) -> None:
        from app.services.dish_chooser import is_dish_chooser_required

        self.assertTrue(
            is_dish_chooser_required(
                {"dish_chooser_enabled": True, "dish_chooser_min_party_size": 8},
                party_size=10,
                source="agent",
            )
        )

    def test_missing_enabled_defaults_to_false(self) -> None:
        from app.services.dish_chooser import is_dish_chooser_required

        self.assertFalse(
            is_dish_chooser_required(
                {"dish_chooser_min_party_size": 8},
                party_size=10,
                source="widget",
            )
        )


class TestValidateFreetextDishes(TestCase):
    def test_returns_cleaned_text_for_non_empty_string(self) -> None:
        from app.services.dish_chooser import validate_freetext_dishes

        self.assertEqual(validate_freetext_dishes(" 2x pizza, 1x salad "), "2x pizza, 1x salad")

    def test_raises_for_empty_string(self) -> None:
        from app.services.dish_chooser import validate_freetext_dishes

        with self.assertRaisesRegex(ValueError, "must not be empty"):
            validate_freetext_dishes("")

    def test_raises_for_whitespace_only_string(self) -> None:
        from app.services.dish_chooser import validate_freetext_dishes

        with self.assertRaisesRegex(ValueError, "must not be empty"):
            validate_freetext_dishes("   \n\t  ")


class TestFormatDishesNote(TestCase):
    def test_formats_note_without_existing_notes(self) -> None:
        from app.services.dish_chooser import format_dishes_note

        self.assertEqual(format_dishes_note(None, "2x pizza"), "[Dishes] 2x pizza")

    def test_appends_note_on_new_line_when_existing_notes_present(self) -> None:
        from app.services.dish_chooser import format_dishes_note

        self.assertEqual(
            format_dishes_note("Birthday table", "2x pizza"),
            "Birthday table\n[Dishes] 2x pizza",
        )

    def test_treats_whitespace_only_existing_notes_as_empty(self) -> None:
        from app.services.dish_chooser import format_dishes_note

        self.assertEqual(format_dishes_note("   \n  ", "2x pizza"), "[Dishes] 2x pizza")


class TestDishSubmissionRequest(TestCase):
    def test_accepts_dishes_only(self) -> None:
        from app.schemas.dish_chooser import DishItem, DishSubmissionRequest

        request = DishSubmissionRequest(dishes=[DishItem(menu_item_id="dish-1", quantity=2)])

        assert request.dishes is not None
        self.assertIsNone(request.dishes_text)
        self.assertEqual(request.dishes[0].menu_item_id, "dish-1")

    def test_accepts_dishes_text_only(self) -> None:
        from app.schemas.dish_chooser import DishSubmissionRequest

        request = DishSubmissionRequest(dishes_text="2x pizza")

        self.assertEqual(request.dishes_text, "2x pizza")
        self.assertIsNone(request.dishes)

    def test_rejects_both_dishes_and_text(self) -> None:
        from app.schemas.dish_chooser import DishItem, DishSubmissionRequest

        with self.assertRaises(ValidationError) as cm:
            DishSubmissionRequest(
                dishes=[DishItem(menu_item_id="dish-1", quantity=1)],
                dishes_text="1x pizza",
            )

        self.assertIn("Provide either 'dishes' or 'dishes_text', not both", str(cm.exception))

    def test_rejects_when_neither_is_provided(self) -> None:
        from app.schemas.dish_chooser import DishSubmissionRequest

        with self.assertRaises(ValidationError) as cm:
            DishSubmissionRequest()

        self.assertIn("Either 'dishes' or 'dishes_text' must be provided", str(cm.exception))

    def test_empty_dishes_list_is_treated_as_not_provided(self) -> None:
        from app.schemas.dish_chooser import DishSubmissionRequest

        with self.assertRaises(ValidationError) as cm:
            DishSubmissionRequest(dishes=[])

        self.assertIn("Either 'dishes' or 'dishes_text' must be provided", str(cm.exception))

    def test_whitespace_only_dishes_text_is_treated_as_not_provided(self) -> None:
        from app.schemas.dish_chooser import DishSubmissionRequest

        with self.assertRaises(ValidationError) as cm:
            DishSubmissionRequest(dishes_text="   \n  ")

        self.assertIn("Either 'dishes' or 'dishes_text' must be provided", str(cm.exception))


class TestRestaurantPatchDishFields(TestCase):
    def test_accepts_valid_min_party_size_bounds(self) -> None:
        from app.models.restaurant import RestaurantPatch

        lower = RestaurantPatch(dish_chooser_min_party_size=2)
        upper = RestaurantPatch(dish_chooser_min_party_size=20)

        self.assertEqual(lower.dish_chooser_min_party_size, 2)
        self.assertEqual(upper.dish_chooser_min_party_size, 20)

    def test_rejects_min_party_size_below_range(self) -> None:
        from app.models.restaurant import RestaurantPatch

        with self.assertRaises(ValidationError):
            RestaurantPatch(dish_chooser_min_party_size=1)

    def test_rejects_min_party_size_above_range(self) -> None:
        from app.models.restaurant import RestaurantPatch

        with self.assertRaises(ValidationError):
            RestaurantPatch(dish_chooser_min_party_size=21)

    def test_accepts_valid_max_dishes_bounds(self) -> None:
        from app.models.restaurant import RestaurantPatch

        lower = RestaurantPatch(dish_chooser_max_dishes=1)
        upper = RestaurantPatch(dish_chooser_max_dishes=50)

        self.assertEqual(lower.dish_chooser_max_dishes, 1)
        self.assertEqual(upper.dish_chooser_max_dishes, 50)

    def test_rejects_max_dishes_below_range(self) -> None:
        from app.models.restaurant import RestaurantPatch

        with self.assertRaises(ValidationError):
            RestaurantPatch(dish_chooser_max_dishes=0)

    def test_rejects_max_dishes_above_range(self) -> None:
        from app.models.restaurant import RestaurantPatch

        with self.assertRaises(ValidationError):
            RestaurantPatch(dish_chooser_max_dishes=51)


class TestPublicRestaurantReadDefaults(TestCase):
    def test_defaults_apply_when_optional_dish_chooser_fields_are_omitted(self) -> None:
        from app.routers.public import PublicRestaurantRead

        restaurant = PublicRestaurantRead(name="Dineo", slug="dineo")

        self.assertFalse(restaurant.dish_chooser_enabled)
        self.assertEqual(restaurant.dish_chooser_min_party_size, 8)
        self.assertEqual(restaurant.dish_chooser_max_dishes, 5)
        self.assertFalse(restaurant.dish_chooser_from_menu)

    def test_explicit_dish_chooser_values_override_defaults(self) -> None:
        from app.routers.public import PublicRestaurantRead

        restaurant = PublicRestaurantRead(
            name="Dineo",
            slug="dineo",
            dish_chooser_enabled=True,
            dish_chooser_min_party_size=12,
            dish_chooser_max_dishes=9,
            dish_chooser_from_menu=True,
        )

        self.assertTrue(restaurant.dish_chooser_enabled)
        self.assertEqual(restaurant.dish_chooser_min_party_size, 12)
        self.assertEqual(restaurant.dish_chooser_max_dishes, 9)
        self.assertTrue(restaurant.dish_chooser_from_menu)
