"""TableCombination CRUD — includes CombinedChairConfig toggle endpoint."""

from typing import Any

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import select

from app.auth.tenant import get_current_restaurant, get_tenant_session
from app.models.restaurant import Restaurant
from app.models.table import FloorTable
from app.models.table_combination import CombinedChairConfig, TableCombination
from app.schemas.table_combination import (
    CombinedChairConfigRead,
    CombinedChairConfigUpdate,
    TableCombinationCreate,
    TableCombinationRead,
    TableCombinationUpdate,
)
from app.services.table_combinations import (
    compute_combined_capacity,
    generate_combined_chair_configs,
    suggest_joining_side_disables,
)

router = APIRouter(prefix="/table-combinations", tags=["table-combinations"])


# ── Helpers ──────────────────────────────────────────────────────────────────


async def _load_combo_with_configs(
    session: AsyncSession, combo: TableCombination
) -> TableCombinationRead:
    """Build a TableCombinationRead response with nested chair configs."""
    config_result = await session.execute(
        select(CombinedChairConfig)
        .where(CombinedChairConfig.combination_id == combo.id)
        .order_by(
            CombinedChairConfig.table_id,
            CombinedChairConfig.slot_index,  # type: ignore[arg-type]
        )
    )
    chair_configs = [
        CombinedChairConfigRead(
            id=c.id,
            combination_id=c.combination_id,
            table_id=c.table_id,
            slot_index=c.slot_index,
            side=c.side,
            enabled=c.enabled,
        )
        for c in config_result.scalars().all()
    ]
    return TableCombinationRead(
        id=combo.id,
        restaurant_id=combo.restaurant_id,
        name=combo.name,
        table_ids=combo.table_ids,
        combined_capacity=combo.combined_capacity,
        merged_x=combo.merged_x,
        merged_y=combo.merged_y,
        chair_configs=chair_configs,
    )


async def _validate_table_ids(
    session: AsyncSession, table_ids: list[str], restaurant_id: str
) -> list[FloorTable]:
    """Validate that all table IDs exist and belong to the restaurant.

    Returns the loaded FloorTable instances.
    """
    if not table_ids:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="At least one table_id is required.",
        )

    tables: list[FloorTable] = []
    for table_id in table_ids:
        result = await session.execute(
            select(FloorTable).where(
                FloorTable.id == table_id, FloorTable.restaurant_id == restaurant_id
            )
        )
        table = result.scalar_one_or_none()
        if table is None:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Table '{table_id}' not found.",
            )
        tables.append(table)
    return tables


# ── CRUD ─────────────────────────────────────────────────────────────────────


@router.get("/", response_model=list[TableCombinationRead])
async def list_combinations(
    session: AsyncSession = Depends(get_tenant_session),
    restaurant: Restaurant = Depends(get_current_restaurant),
) -> list[Any]:
    result = await session.execute(
        select(TableCombination).where(TableCombination.restaurant_id == restaurant.id)
    )
    combos = list(result.scalars().all())
    return [await _load_combo_with_configs(session, c) for c in combos]


@router.post("/", response_model=TableCombinationRead, status_code=status.HTTP_201_CREATED)
async def create_combination(
    payload: TableCombinationCreate,
    session: AsyncSession = Depends(get_tenant_session),
    restaurant: Restaurant = Depends(get_current_restaurant),
) -> Any:
    # Validate all table IDs
    tables = await _validate_table_ids(session, payload.table_ids, restaurant.id)

    # Create the combination
    combo = TableCombination(
        name=payload.name,
        table_ids=payload.table_ids,
        restaurant_id=restaurant.id,
    )
    session.add(combo)
    await session.flush()  # need combo.id for chair configs

    # Auto-generate CombinedChairConfig with joining-side suggestions
    suggested_disables = suggest_joining_side_disables(tables)
    configs = generate_combined_chair_configs(combo, tables, suggested_disables)
    for config in configs:
        session.add(config)
    await session.flush()

    # Compute combined_capacity from enabled configs
    combo.combined_capacity = await compute_combined_capacity(session, combo.id)
    session.add(combo)

    await session.commit()
    await session.refresh(combo)
    return await _load_combo_with_configs(session, combo)


@router.put("/{combo_id}", response_model=TableCombinationRead)
async def update_combination(
    combo_id: str,
    payload: TableCombinationUpdate,
    session: AsyncSession = Depends(get_tenant_session),
    restaurant: Restaurant = Depends(get_current_restaurant),
) -> Any:
    result = await session.execute(
        select(TableCombination).where(
            TableCombination.id == combo_id,
            TableCombination.restaurant_id == restaurant.id,
        )
    )
    combo = result.scalar_one_or_none()
    if combo is None:
        raise HTTPException(status_code=404, detail="Combination not found")

    # Apply provided fields. `TableCombinationUpdate` is fully partial so
    # callers may patch only `merged_x` / `merged_y` (high-frequency drag
    # save), only `name`, only `table_ids`, or any combination.
    if payload.name is not None:
        combo.name = payload.name
    if payload.merged_x is not None:
        combo.merged_x = payload.merged_x
    if payload.merged_y is not None:
        combo.merged_y = payload.merged_y

    # If table_ids changed, regenerate chair configs
    if payload.table_ids is not None and payload.table_ids != combo.table_ids:
        tables = await _validate_table_ids(session, payload.table_ids, restaurant.id)
        combo.table_ids = payload.table_ids

        # Delete existing configs
        old_configs = await session.execute(
            select(CombinedChairConfig).where(CombinedChairConfig.combination_id == combo_id)
        )
        for old_config in old_configs.scalars().all():
            await session.delete(old_config)
        await session.flush()

        # Regenerate
        suggested_disables = suggest_joining_side_disables(tables)
        new_configs = generate_combined_chair_configs(combo, tables, suggested_disables)
        for config in new_configs:
            session.add(config)
        await session.flush()

        combo.combined_capacity = await compute_combined_capacity(session, combo.id)

    session.add(combo)
    await session.commit()
    await session.refresh(combo)
    return await _load_combo_with_configs(session, combo)


@router.delete("/{combo_id}", status_code=status.HTTP_200_OK)
async def delete_combination(
    combo_id: str,
    session: AsyncSession = Depends(get_tenant_session),
    restaurant: Restaurant = Depends(get_current_restaurant),
) -> dict[str, str]:
    result = await session.execute(
        select(TableCombination).where(
            TableCombination.id == combo_id,
            TableCombination.restaurant_id == restaurant.id,
        )
    )
    combo = result.scalar_one_or_none()
    if combo is None:
        raise HTTPException(status_code=404, detail="Combination not found")

    # Cascade delete chair configs
    config_result = await session.execute(
        select(CombinedChairConfig).where(CombinedChairConfig.combination_id == combo_id)
    )
    for config in config_result.scalars().all():
        await session.delete(config)

    await session.delete(combo)
    await session.commit()
    return {"status": "deleted"}


# ── Chair config toggle ──────────────────────────────────────────────────────


@router.patch(
    "/{combo_id}/chairs/{config_id}",
    response_model=CombinedChairConfigRead,
)
async def toggle_chair_config(
    combo_id: str,
    config_id: str,
    payload: CombinedChairConfigUpdate,
    session: AsyncSession = Depends(get_tenant_session),
    restaurant: Restaurant = Depends(get_current_restaurant),
) -> Any:
    # Verify combo belongs to restaurant
    combo_result = await session.execute(
        select(TableCombination).where(
            TableCombination.id == combo_id,
            TableCombination.restaurant_id == restaurant.id,
        )
    )
    combo = combo_result.scalar_one_or_none()
    if combo is None:
        raise HTTPException(status_code=404, detail="Combination not found")

    # Verify config belongs to combo
    result = await session.execute(
        select(CombinedChairConfig).where(
            CombinedChairConfig.id == config_id,
            CombinedChairConfig.combination_id == combo_id,
        )
    )
    config = result.scalar_one_or_none()
    if config is None:
        raise HTTPException(status_code=404, detail="Chair config not found")

    config.enabled = payload.enabled
    session.add(config)
    await session.flush()

    # Recompute combined_capacity
    combo.combined_capacity = await compute_combined_capacity(session, combo_id)
    session.add(combo)

    await session.commit()
    await session.refresh(config)
    return config
