from datetime import date

from fastapi import HTTPException, status
from sqlalchemy import select
from sqlalchemy.orm import Session, joinedload

from app.models.booking import WhatsAppBookingSession
from app.models.customer import Customer
from app.models.reservation import Reservation, ReservationStatus, ReservationStatusHistory
from app.models.room import Room
from app.models.table import Table
from app.models.venue import Venue
from app.schemas.reservation import ReservationCreate, ReservationRead, ReservationUpdate
from app.services.assignment import reassign_single_reservation
from app.services.layout_units import LAYOUT_UNITS_PER_METER
from app.services.seed_service import ensure_room_for_venue


def get_default_room(venue_id: int, db: Session) -> Room:
    room = ensure_room_for_venue(venue_id, db)
    db.flush()
    return room


def get_customer(customer_id: int, db: Session) -> Customer:
    customer = db.get(Customer, customer_id)
    if customer is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cliente non trovato")
    return customer


def find_or_create_customer(payload: ReservationCreate, db: Session) -> Customer:
    if payload.customer_id is not None:
        return get_customer(payload.customer_id, db)

    stmt = select(Customer).where(Customer.phone == payload.customer_phone, Customer.name == payload.customer_name)
    existing = db.scalar(stmt)
    if existing is not None:
        if payload.customer_email and existing.email != payload.customer_email:
            existing.email = payload.customer_email
        if payload.customer_notes:
            existing.notes = payload.customer_notes
        return existing

    customer = Customer(
        name=payload.customer_name or "",
        phone=payload.customer_phone or "",
        email=payload.customer_email,
        notes=payload.customer_notes,
    )
    db.add(customer)
    db.flush()
    return customer


def append_status_history(
    reservation: Reservation,
    new_status: ReservationStatus,
    db: Session,
    old_status: ReservationStatus | None = None,
) -> None:
    history = ReservationStatusHistory(
        reservation_id=reservation.id,
        old_status=old_status,
        new_status=new_status,
    )
    db.add(history)


def create_reservation(payload: ReservationCreate, db: Session) -> Reservation:
    venue = db.get(Venue, payload.venue_id)
    if venue is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Locale non trovato")

    customer = find_or_create_customer(payload, db)
    reservation = Reservation(
        venue_id=payload.venue_id,
        customer_id=customer.id,
        reservation_date=payload.reservation_date,
        start_time=payload.start_time,
        duration_minutes=payload.duration_minutes,
        guests=payload.guests,
        status=payload.status,
        source=payload.source,
        notes=payload.notes,
        area_preference=payload.area_preference,
    )
    db.add(reservation)
    db.flush()
    append_status_history(reservation, reservation.status, db)
    db.flush()
    reassign_single_reservation(reservation, db)
    db.commit()
    return get_reservation_or_404(reservation.id, db)


def get_reservation_or_404(reservation_id: int, db: Session, *, venue_id: int | None = None) -> Reservation:
    stmt = (
        select(Reservation)
        .options(
            joinedload(Reservation.customer),
            joinedload(Reservation.assigned_table),
            joinedload(Reservation.assigned_combination),
            joinedload(Reservation.status_history),
        )
        .where(Reservation.id == reservation_id)
    )
    if venue_id is not None:
        stmt = stmt.where(Reservation.venue_id == venue_id)
    reservation = db.scalar(stmt)
    if reservation is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Prenotazione non trovata")
    return reservation


def list_reservations(
    db: Session,
    reservation_date: date | None = None,
    status_filter: ReservationStatus | None = None,
    venue_id: int | None = None,
) -> list[Reservation]:
    stmt = (
        select(Reservation)
        .options(
            joinedload(Reservation.customer),
            joinedload(Reservation.assigned_table),
            joinedload(Reservation.assigned_combination),
            joinedload(Reservation.status_history),
        )
        .order_by(Reservation.reservation_date.desc(), Reservation.start_time, Reservation.created_at)
    )
    if venue_id is not None:
        stmt = stmt.where(Reservation.venue_id == venue_id)
    if reservation_date is not None:
        stmt = stmt.where(Reservation.reservation_date == reservation_date)
    if status_filter is not None:
        stmt = stmt.where(Reservation.status == status_filter)
    return list(db.scalars(stmt).unique())


def update_reservation(reservation_id: int, payload: ReservationUpdate, db: Session, *, venue_id: int | None = None) -> Reservation:
    reservation = get_reservation_or_404(reservation_id, db, venue_id=venue_id)
    previous_status = reservation.status
    needs_reassignment = False
    payload_data = payload.model_dump(exclude_unset=True)

    customer_updates = {
        field: payload_data.pop(field)
        for field in ("customer_name", "customer_phone", "customer_email", "customer_notes")
        if field in payload_data
    }

    if "customer_id" in payload_data:
        customer_id = payload_data.pop("customer_id")
        if customer_id is not None:
            reservation.customer = get_customer(customer_id, db)

    customer = reservation.customer
    if customer is not None:
        if "customer_name" in customer_updates and customer_updates["customer_name"] is not None:
            customer.name = customer_updates["customer_name"]
        if "customer_phone" in customer_updates and customer_updates["customer_phone"] is not None:
            customer.phone = customer_updates["customer_phone"]
        if "customer_email" in customer_updates:
            customer.email = customer_updates["customer_email"]
        if "customer_notes" in customer_updates:
            customer.notes = customer_updates["customer_notes"]

    for field, value in payload_data.items():
        setattr(reservation, field, value)
        if field in {"reservation_date", "start_time", "duration_minutes", "guests"}:
            needs_reassignment = True
        if field == "status" and value != previous_status:
            append_status_history(reservation, value, db, old_status=previous_status)
            if value in {ReservationStatus.cancelled, ReservationStatus.no_show}:
                reservation.assigned_table_id = None
                reservation.assigned_combination_id = None

    db.flush()

    if needs_reassignment and reservation.status not in {ReservationStatus.cancelled, ReservationStatus.no_show}:
        reassign_single_reservation(reservation, db)

    db.commit()
    return get_reservation_or_404(reservation_id, db, venue_id=venue_id)


def delete_reservation(reservation_id: int, db: Session, *, venue_id: int | None = None) -> dict[str, object]:
    reservation = get_reservation_or_404(reservation_id, db, venue_id=venue_id)
    snapshot = {
        "id": reservation.id,
        "customer_name": reservation.customer.name,
        "reservation_date": reservation.reservation_date.isoformat(),
        "start_time": reservation.start_time.strftime("%H:%M"),
    }

    linked_sessions = list(
        db.scalars(select(WhatsAppBookingSession).where(WhatsAppBookingSession.reservation_id == reservation.id))
    )
    for session in linked_sessions:
        session.reservation_id = None
        session.status = "cancelled"
        session.draft = {}

    db.delete(reservation)
    db.commit()
    return snapshot


def assignment_label(reservation: Reservation) -> str | None:
    if reservation.assigned_table is not None:
        return reservation.assigned_table.name
    if reservation.assigned_combination is not None:
        return reservation.assigned_combination.name
    return None


def room_tables_map(venue_id: int, db: Session) -> dict[int, Table]:
    room_ids = list(db.scalars(select(Room.id).where(Room.venue_id == venue_id)))
    if not room_ids:
        room = get_default_room(venue_id, db)
        room_ids = [room.id]
    tables = list(db.scalars(select(Table).where(Table.room_id.in_(room_ids))))
    return {table.id: table for table in tables}


def _axis_instruction(anchor: Table, target: Table) -> str:
    anchor_center_x = anchor.x + (anchor.width / 2)
    anchor_center_y = anchor.y + (anchor.height / 2)
    target_center_x = target.x + (target.width / 2)
    target_center_y = target.y + (target.height / 2)
    delta_x = target_center_x - anchor_center_x
    delta_y = target_center_y - anchor_center_y

    if abs(delta_x) >= abs(delta_y):
        move_direction = "left" if delta_x > 0 else "right"
        final_side = "right side of" if delta_x > 0 else "left side of"
        current_gap = max(0, abs(delta_x) - ((anchor.width + target.width) / 2))
    else:
        move_direction = "up" if delta_y > 0 else "down"
        final_side = "below" if delta_y > 0 else "above"
        current_gap = max(0, abs(delta_y) - ((anchor.height + target.height) / 2))

    gap_meters = round(current_gap / LAYOUT_UNITS_PER_METER, 2)
    direction_labels = {
        "left": "verso sinistra",
        "right": "verso destra",
        "up": "verso l'alto",
        "down": "verso il basso",
    }
    side_labels = {
        "right side of": "lato destro di",
        "left side of": "lato sinistro di",
        "below": "sotto",
        "above": "sopra",
    }
    gap_text = f" Riduci di circa {gap_meters} m." if current_gap > 0 else ""
    return f"Sposta il tavolo {target.name} {direction_labels[move_direction]} fino a portarlo sul {side_labels[final_side]} tavolo {anchor.name}.{gap_text}"


def build_service_plan(reservation: Reservation, db: Session) -> tuple[bool, str | None, list[str]]:
    if reservation.assigned_combination is None:
        return False, None, []

    tables_by_id = room_tables_map(reservation.venue_id, db)
    involved_tables = [
        tables_by_id[table_id]
        for table_id in reservation.assigned_combination.table_ids
        if table_id in tables_by_id
    ]
    if not involved_tables:
        summary = f"Prepara la combinazione {reservation.assigned_combination.name} per {reservation.guests} coperti."
        return True, summary, []

    ordered_tables = sorted(involved_tables, key=lambda table: (table.x, table.y, table.name))
    anchor = ordered_tables[0]
    table_names = ", ".join(table.name for table in ordered_tables)
    steps = [f"Lascia il tavolo {anchor.name} come posizione di riferimento."]
    previous_table = anchor
    for table in ordered_tables[1:]:
        steps.append(_axis_instruction(previous_table, table))
        previous_table = table
    steps.append(
        f"Dopo l'unione, verifica che l'assetto finale ospiti {reservation.guests} coperti senza bloccare il passaggio di servizio."
    )
    summary = f"Unisci i tavoli {table_names} per la prenotazione {reservation.id} ({reservation.guests} coperti)."
    return True, summary, steps


def serialize_reservation(reservation: Reservation, db: Session) -> ReservationRead:
    payload = ReservationRead.model_validate(reservation)
    requires_table_join, service_summary, service_steps = build_service_plan(reservation, db)
    return payload.model_copy(
        update={
            "requires_table_join": requires_table_join,
            "service_summary": service_summary,
            "service_steps": service_steps,
        }
    )
