"""Shared validation and formatting logic for the dish chooser feature."""

from typing import Any

from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import select

from app.models.menu_item import MenuItem

# Sources that require dish selection when the feature is enabled
_GATED_SOURCES = frozenset({"widget", "agent"})


def is_dish_chooser_required(
    settings: dict[str, Any],
    party_size: int,
    source: str,
) -> bool:
    """Check whether a reservation requires dish selection.

    Returns True when:
    - dish_chooser_enabled is truthy in settings
    - party_size >= dish_chooser_min_party_size (default 8)
    - source is one of the gated channels (widget, agent)
    """
    if not settings.get("dish_chooser_enabled"):
        return False
    min_size = int(settings.get("dish_chooser_min_party_size", 8))
    return party_size >= min_size and source in _GATED_SOURCES


async def validate_menu_dishes(
    dishes: list[dict[str, Any]],
    restaurant_id: str,
    party_size: int,
    max_dishes: int,
    session: AsyncSession,
) -> str:
    """Validate menu-mode dish items and return formatted note text.

    Each item in `dishes` must have:
    - menu_item_id: str (UUID of an active MenuItem)
    - quantity: int >= 1

    Raises ValueError with descriptive message on failure.
    Returns the formatted note string (without [Dishes] prefix).
    """
    if not dishes:
        raise ValueError("At least one dish must be selected")

    # Extract, validate structure, and aggregate duplicates
    quantities: dict[str, int] = {}
    for dish in dishes:
        mid = dish.get("menu_item_id")
        qty = dish.get("quantity", 0)
        if not mid or not isinstance(qty, int) or qty < 1:
            raise ValueError("Invalid dish entry: each must have menu_item_id and quantity >= 1")
        quantities[mid] = quantities.get(mid, 0) + qty

    # Check distinct count
    item_ids = list(quantities.keys())
    if len(item_ids) > max_dishes:
        raise ValueError(
            f"Too many distinct dishes: {len(item_ids)} selected, maximum is {max_dishes}"
        )

    # Check total equals party size
    total = sum(quantities.values())
    if total != party_size:
        raise ValueError(f"Total dish quantity ({total}) must equal party size ({party_size})")

    # Fetch and validate menu items
    result = await session.execute(
        select(MenuItem).where(
            MenuItem.id.in_(item_ids),  # type: ignore[attr-defined]
            MenuItem.restaurant_id == restaurant_id,
        )
    )
    found_items = {item.id: item for item in result.scalars().all()}

    # Verify all items exist, belong to restaurant, and are active
    parts: list[str] = []
    for mid in item_ids:
        item = found_items.get(mid)
        if item is None:
            raise ValueError(f"Menu item not found or does not belong to this restaurant: {mid}")
        if not item.is_active:
            raise ValueError(f"Menu item is not active: {item.name}")
        parts.append(f"{quantities[mid]}x {item.name}")

    return ", ".join(parts)


def validate_freetext_dishes(dishes_text: str) -> str:
    """Validate free-text dish description and return the cleaned text.

    Raises ValueError if empty or whitespace-only.
    """
    cleaned = dishes_text.strip()
    if not cleaned:
        raise ValueError("Dish description must not be empty")
    return cleaned


def format_dishes_note(existing_notes: str | None, dish_note: str) -> str:
    """Append a [Dishes] tagged note to existing reservation notes.

    If existing_notes is non-empty, the dish note is appended on a new line.
    """
    tagged = f"[Dishes] {dish_note}"
    if existing_notes and existing_notes.strip():
        return f"{existing_notes.strip()}\n{tagged}"
    return tagged
