from datetime import date, datetime

from sqlalchemy import select
from sqlalchemy.orm import Session, joinedload

from app.models.reservation import Reservation, ReservationStatus
from app.models.room import Room
from app.models.table import Table, TableCombination
from app.schemas.table import TableCombinationRead
from app.schemas.floor_plan import FloorPlanReservation, FloorPlanResponse, OccupancyWindow, TableFloorState
from app.services.assignment import reservation_window
from app.services.layout_units import serialize_room, serialize_table
from app.services.reservation_service import assignment_label, build_service_plan


def build_floor_plan(day: date, room_id: int, db: Session, *, venue_id: int | None = None) -> FloorPlanResponse:
    room = db.get(Room, room_id)
    if room is None:
        raise ValueError("Sala non trovata")
    if venue_id is not None and room.venue_id != venue_id:
        raise ValueError("Sala non trovata")

    tables = list(db.scalars(select(Table).where(Table.room_id == room_id).order_by(Table.name)))
    combinations = list(
        db.scalars(select(TableCombination).where(TableCombination.room_id == room_id).order_by(TableCombination.name))
    )
    reservations = list(
        db.scalars(
            select(Reservation)
            .options(
                joinedload(Reservation.customer),
                joinedload(Reservation.assigned_table),
                joinedload(Reservation.assigned_combination),
            )
            .where(Reservation.reservation_date == day, Reservation.venue_id == room.venue_id)
            .order_by(Reservation.start_time)
        ).unique()
    )

    now = datetime.now()
    current_day = now.date()
    floor_reservations: list[FloorPlanReservation] = []
    table_states: list[TableFloorState] = []

    for reservation in reservations:
        requires_table_join, service_summary, service_steps = build_service_plan(reservation, db)
        floor_reservations.append(
            FloorPlanReservation(
                reservation_id=reservation.id,
                customer_name=reservation.customer.name,
                guests=reservation.guests,
                start_time=reservation.start_time,
                duration_minutes=reservation.duration_minutes,
                status=reservation.status,
                assigned_table_id=reservation.assigned_table_id,
                assigned_combination_id=reservation.assigned_combination_id,
                assignment_label=assignment_label(reservation),
                requires_table_join=requires_table_join,
                service_summary=service_summary,
                service_steps=service_steps,
            )
        )

    for table in tables:
        windows: list[OccupancyWindow] = []
        occupied_now = False
        for reservation in reservations:
            if reservation.status in {ReservationStatus.cancelled, ReservationStatus.no_show}:
                continue
            assigned_ids = []
            if reservation.assigned_table_id is not None:
                assigned_ids = [reservation.assigned_table_id]
            elif reservation.assigned_combination is not None:
                assigned_ids = reservation.assigned_combination.table_ids
            if table.id not in assigned_ids:
                continue
            start_at, end_at = reservation_window(reservation)
            windows.append(
                OccupancyWindow(
                    reservation_id=reservation.id,
                    customer_name=reservation.customer.name,
                    guests=reservation.guests,
                    status=reservation.status,
                    start_time=start_at.time(),
                    end_time=end_at.time(),
                )
            )
            if day == current_day and start_at <= now < end_at:
                occupied_now = True
        table_states.append(
            TableFloorState(
                table_id=table.id,
                table_name=table.name,
                is_occupied_now=occupied_now,
                occupancy_windows=windows,
            )
        )

    return FloorPlanResponse(
        date=day,
        room=serialize_room(room),
        tables=[serialize_table(table) for table in tables],
        table_combinations=[TableCombinationRead.model_validate(combination) for combination in combinations],
        reservations=floor_reservations,
        table_states=table_states,
    )
