from datetime import date

from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from fastapi.responses import PlainTextResponse
from sqlalchemy import or_, select
from sqlalchemy.orm import Session

from app.api.deps import get_current_venue, get_db
from app.models.customer import Customer
from app.models.reservation import Reservation, ReservationStatus
from app.models.room import Room
from app.models.table import Table, TableCombination
from app.models.venue import Venue
from app.schemas.booking import VenueBookingSettingsRead, VenueBookingSettingsUpdate
from app.schemas.customer import CustomerCreate, CustomerRead
from app.schemas.floor_plan import FloorPlanResponse
from app.schemas.portal import PortalContextSyncRequest, PortalContextSyncResponse
from app.schemas.reservation import (
    AssignmentResult,
    RecalculateDayRequest,
    ReservationCreate,
    ReservationListResponse,
    ReservationRead,
    ReservationUpdate,
)
from app.schemas.room import RoomCreate, RoomRead, RoomUpdate
from app.schemas.table import TableCreate, TableRead, TableUpdate
from app.schemas.venue import VenueRead
from app.schemas.whatsapp import (
    WhatsAppAssistantTurnRead,
    WhatsAppConfigValidationResponse,
    WhatsAppConversationSummaryRead,
    WhatsAppEventLogRead,
    WhatsAppSendTestRequest,
    WhatsAppSendTestResponse,
    WhatsAppStatusResponse,
    WhatsAppWebhookAck,
)
from app.services.assignment import recalculate_day_assignments, reassign_single_reservation
from app.services.booking_settings_service import (
    get_booking_settings,
    serialize_booking_settings,
    update_booking_settings,
)
from app.services.floor_plan_service import build_floor_plan
from app.services.layout_units import meters_to_units, serialize_room, serialize_table
from app.services.reservation_service import (
    create_reservation,
    delete_reservation,
    get_default_room,
    get_reservation_or_404,
    list_reservations,
    serialize_reservation,
    update_reservation,
)
from app.services.seed_service import ensure_room_for_venue, reset_room_layout, seed_demo_data
from app.services.tenant_sync_service import sync_venue_name_from_portal
from app.services.whatsapp_service import (
    get_whatsapp_status,
    list_whatsapp_assistant_turns,
    list_whatsapp_conversations,
    list_whatsapp_logs,
    parse_webhook_body,
    process_webhook_payload,
    send_test_whatsapp_message,
    verify_webhook_handshake,
    validate_webhook_signature,
    validate_whatsapp_config,
)


router = APIRouter()


def ensure_current_venue_matches(requested_venue_id: int | None, current_venue: Venue) -> int:
    if requested_venue_id is None:
        return current_venue.id
    if requested_venue_id != current_venue.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Locale non trovato per il tenant attivo")
    return requested_venue_id


def build_table_storage_payload(payload: TableCreate | TableUpdate) -> dict[str, object]:
    normalized_name = payload.name.strip()
    if not normalized_name:
        raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Nome tavolo obbligatorio")
    return {
        "room_id": payload.room_id,
        "name": normalized_name,
        "x": meters_to_units(payload.x, allow_zero=True),
        "y": meters_to_units(payload.y, allow_zero=True),
        "width": meters_to_units(payload.width),
        "height": meters_to_units(payload.height),
        "shape": payload.shape,
        "rotation_degrees": round(payload.rotation_degrees, 1),
        "min_seats": payload.min_seats,
        "max_seats": payload.max_seats,
        "join_group": payload.join_group,
        "is_active": payload.is_active,
    }


def normalize_room_name(raw_name: str) -> str:
    normalized = raw_name.strip()
    if not normalized:
        raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Nome area obbligatorio")
    return normalized


def build_room_storage_payload(payload: RoomCreate | RoomUpdate) -> dict[str, object]:
    width_units = meters_to_units(payload.width)
    height_units = meters_to_units(payload.height)
    counter_width_units = (
        meters_to_units(payload.counter_width) if payload.counter_width is not None else min(180, width_units)
    )
    counter_height_units = (
        meters_to_units(payload.counter_height) if payload.counter_height is not None else min(280, height_units)
    )
    max_counter_x = max(width_units - counter_width_units, 0)
    max_counter_y = max(height_units - counter_height_units, 0)
    counter_x_units = (
        meters_to_units(payload.counter_x, allow_zero=True) if payload.counter_x is not None else max_counter_x
    )
    counter_y_units = (
        meters_to_units(payload.counter_y, allow_zero=True) if payload.counter_y is not None else min(220, max_counter_y)
    )
    entrance_width_units = (
        meters_to_units(payload.entrance_width) if payload.entrance_width is not None else min(260, width_units)
    )
    entrance_height_units = (
        meters_to_units(payload.entrance_height) if payload.entrance_height is not None else min(180, height_units)
    )
    max_entrance_x = max(width_units - entrance_width_units, 0)
    max_entrance_y = max(height_units - entrance_height_units, 0)
    entrance_x_units = (
        meters_to_units(payload.entrance_x, allow_zero=True)
        if payload.entrance_x is not None
        else max((width_units - entrance_width_units) // 2, 0)
    )
    entrance_y_units = (
        meters_to_units(payload.entrance_y, allow_zero=True)
        if payload.entrance_y is not None
        else max(height_units - entrance_height_units - 60, 0)
    )

    return {
        "name": normalize_room_name(payload.name),
        "width": width_units,
        "height": height_units,
        "counter_name": (payload.counter_name or "Banco").strip() or "Banco",
        "counter_x": min(counter_x_units, max_counter_x),
        "counter_y": min(counter_y_units, max_counter_y),
        "counter_width": min(counter_width_units, width_units),
        "counter_height": min(counter_height_units, height_units),
        "counter_visible": payload.counter_visible,
        "entrance_name": (payload.entrance_name or "Entrata").strip() or "Entrata",
        "entrance_x": min(entrance_x_units, max_entrance_x),
        "entrance_y": min(entrance_y_units, max_entrance_y),
        "entrance_width": min(entrance_width_units, width_units),
        "entrance_height": min(entrance_height_units, height_units),
        "entrance_visible": payload.entrance_visible,
        "background_image_data_url": (payload.background_image_data_url or "").strip() or None,
    }


@router.get("/health")
def health_check() -> dict[str, str]:
    return {"status": "ok"}


@router.get("/venues", response_model=list[VenueRead])
def get_venues(current_venue: Venue = Depends(get_current_venue)) -> list[Venue]:
    return [current_venue]


@router.get("/rooms", response_model=list[RoomRead])
def get_rooms(
    venue_id: int | None = None,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> list[RoomRead]:
    allowed_venue_id = ensure_current_venue_matches(venue_id, current_venue)
    stmt = select(Room).order_by(Room.name)
    stmt = stmt.where(Room.venue_id == allowed_venue_id)
    rooms = list(db.scalars(stmt))
    if not rooms:
        room = ensure_room_for_venue(allowed_venue_id, db)
        db.commit()
        db.refresh(room)
        rooms = [room]
    return [serialize_room(room) for room in rooms]


@router.post("/rooms", response_model=RoomRead, status_code=status.HTTP_201_CREATED)
def create_room(
    payload: RoomCreate,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> RoomRead:
    room = Room(venue_id=current_venue.id, **build_room_storage_payload(payload))
    db.add(room)
    db.commit()
    db.refresh(room)
    return serialize_room(room)


@router.put("/rooms/{room_id}", response_model=RoomRead)
def update_room(
    room_id: int,
    payload: RoomUpdate,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> RoomRead:
    room = db.get(Room, room_id)
    if room is None or room.venue_id != current_venue.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Sala non trovata")

    for field, value in build_room_storage_payload(payload).items():
        setattr(room, field, value)

    db.commit()
    db.refresh(room)
    return serialize_room(room)


@router.post("/rooms/{room_id}/reset-layout", response_model=RoomRead)
def reset_room(
    room_id: int,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> RoomRead:
    room = db.get(Room, room_id)
    if room is None or room.venue_id != current_venue.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Sala non trovata")

    reset_room_layout(room, db)
    db.commit()
    db.refresh(room)
    return serialize_room(room)


@router.get("/booking-settings", response_model=VenueBookingSettingsRead)
def read_booking_settings(
    venue_id: int | None = None,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> VenueBookingSettingsRead:
    try:
        venue, settings = get_booking_settings(db, venue_id=ensure_current_venue_matches(venue_id, current_venue))
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc

    db.commit()
    db.refresh(settings)
    return serialize_booking_settings(venue, settings)


@router.post("/portal-context/sync", response_model=PortalContextSyncResponse)
def sync_portal_context(payload: PortalContextSyncRequest, db: Session = Depends(get_db)) -> PortalContextSyncResponse:
    venue = sync_venue_name_from_portal(session_token=payload.session_token, db=db)
    return PortalContextSyncResponse(synced=True, venue_id=venue.id, venue_name=venue.name)


@router.put("/booking-settings/{venue_id}", response_model=VenueBookingSettingsRead)
def write_booking_settings(
    venue_id: int,
    payload: VenueBookingSettingsUpdate,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> VenueBookingSettingsRead:
    try:
        venue, settings = update_booking_settings(ensure_current_venue_matches(venue_id, current_venue), payload, db)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc

    return serialize_booking_settings(venue, settings)


@router.get("/tables", response_model=list[TableRead])
def get_tables(
    room_id: int | None = None,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> list[TableRead]:
    stmt = select(Table).order_by(Table.name)
    if room_id is not None:
        room = db.get(Room, room_id)
        if room is None or room.venue_id != current_venue.id:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Sala non trovata")
        stmt = stmt.where(Table.room_id == room_id)
    else:
        room = get_default_room(current_venue.id, db)
        stmt = stmt.where(Table.room_id == room.id)
    tables = list(db.scalars(stmt))
    return [serialize_table(table) for table in tables]


@router.post("/tables", response_model=TableRead, status_code=status.HTTP_201_CREATED)
def create_table(
    payload: TableCreate,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> TableRead:
    room = db.get(Room, payload.room_id)
    if room is None or room.venue_id != current_venue.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Sala non trovata")
    table = Table(**build_table_storage_payload(payload))
    db.add(table)
    db.commit()
    db.refresh(table)
    return serialize_table(table)


@router.put("/tables/{table_id}", response_model=TableRead)
def update_table(
    table_id: int,
    payload: TableUpdate,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> TableRead:
    table = db.get(Table, table_id)
    if table is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tavolo non trovato")
    room = db.get(Room, table.room_id)
    if room is None or room.venue_id != current_venue.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tavolo non trovato")
    target_room = db.get(Room, payload.room_id)
    if target_room is None or target_room.venue_id != current_venue.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Sala non trovata")
    for field, value in build_table_storage_payload(payload).items():
        setattr(table, field, value)
    db.commit()
    db.refresh(table)
    return serialize_table(table)


@router.delete("/tables/{table_id}")
def delete_table(
    table_id: int,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> dict[str, object]:
    table = db.get(Table, table_id)
    if table is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tavolo non trovato")

    room = db.get(Room, table.room_id)
    if room is None or room.venue_id != current_venue.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tavolo non trovato")

    room_combinations = list(
        db.scalars(select(TableCombination).where(TableCombination.room_id == room.id).order_by(TableCombination.id))
    )
    combinations_to_remove = [
        combination for combination in room_combinations if table_id in (combination.table_ids or [])
    ]
    combination_ids = [combination.id for combination in combinations_to_remove]

    blocking_reservation = db.scalar(
        select(Reservation.id)
        .where(
            Reservation.venue_id == current_venue.id,
            Reservation.status.in_(
                [ReservationStatus.pending, ReservationStatus.confirmed, ReservationStatus.seated]
            ),
            or_(
                Reservation.assigned_table_id == table.id,
                Reservation.assigned_combination_id.in_(combination_ids) if combination_ids else False,
            ),
        )
        .limit(1)
    )
    if blocking_reservation is not None:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=(
                "Questo tavolo e ancora assegnato a prenotazioni attive. "
                "Sposta o riassegna prima le prenotazioni collegate."
            ),
        )

    reservations_to_clear = list(
        db.scalars(
            select(Reservation).where(
                Reservation.venue_id == current_venue.id,
                or_(
                    Reservation.assigned_table_id == table.id,
                    Reservation.assigned_combination_id.in_(combination_ids) if combination_ids else False,
                ),
            )
        )
    )
    for reservation in reservations_to_clear:
        if reservation.assigned_table_id == table.id:
            reservation.assigned_table_id = None
        if reservation.assigned_combination_id in combination_ids:
            reservation.assigned_combination_id = None

    for combination in combinations_to_remove:
        db.delete(combination)

    removed_combinations = len(combinations_to_remove)
    cleared_reservations = len(reservations_to_clear)
    deleted_table_name = table.name
    db.delete(table)
    db.commit()

    detail = f'Tavolo "{deleted_table_name}" eliminato.'
    if removed_combinations:
        detail += f" Rimosse anche {removed_combinations} combinazioni collegate."
    if cleared_reservations:
        detail += f" Ho liberato {cleared_reservations} prenotazioni storiche collegate."

    return {
        "deleted_id": table_id,
        "removed_combinations": removed_combinations,
        "cleared_reservations": cleared_reservations,
        "detail": detail,
    }


@router.get("/customers", response_model=list[CustomerRead])
def get_customers(db: Session = Depends(get_db), current_venue: Venue = Depends(get_current_venue)) -> list[Customer]:
    stmt = (
        select(Customer)
        .join(Customer.reservations)
        .where(Reservation.venue_id == current_venue.id)
        .order_by(Customer.name)
        .distinct()
    )
    return list(db.scalars(stmt))


@router.post("/customers", response_model=CustomerRead, status_code=status.HTTP_201_CREATED)
def create_customer(payload: CustomerCreate, db: Session = Depends(get_db)) -> Customer:
    customer = Customer(**payload.model_dump())
    db.add(customer)
    db.commit()
    db.refresh(customer)
    return customer


@router.get("/reservations", response_model=ReservationListResponse)
def get_reservations(
    reservation_date: date | None = Query(default=None),
    status_filter: ReservationStatus | None = Query(default=None, alias="status"),
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> ReservationListResponse:
    reservations = list_reservations(db, reservation_date=reservation_date, status_filter=status_filter, venue_id=current_venue.id)
    items = [serialize_reservation(reservation, db) for reservation in reservations]
    return ReservationListResponse(items=items, total=len(items))


@router.post("/reservations", response_model=ReservationRead, status_code=status.HTTP_201_CREATED)
def post_reservation(payload: ReservationCreate, db: Session = Depends(get_db), current_venue: Venue = Depends(get_current_venue)) -> ReservationRead:
    if payload.venue_id != current_venue.id:
        payload = payload.model_copy(update={"venue_id": current_venue.id})
    reservation = create_reservation(payload, db)
    return serialize_reservation(reservation, db)


@router.get("/reservations/{reservation_id}", response_model=ReservationRead)
def get_reservation(
    reservation_id: int,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> ReservationRead:
    reservation = get_reservation_or_404(reservation_id, db, venue_id=current_venue.id)
    return serialize_reservation(reservation, db)


@router.put("/reservations/{reservation_id}", response_model=ReservationRead)
def put_reservation(
    reservation_id: int,
    payload: ReservationUpdate,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> ReservationRead:
    reservation = update_reservation(reservation_id, payload, db, venue_id=current_venue.id)
    return serialize_reservation(reservation, db)


@router.delete("/reservations/{reservation_id}")
def remove_reservation(
    reservation_id: int,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> dict[str, object]:
    deleted = delete_reservation(reservation_id, db, venue_id=current_venue.id)
    return {
        "deleted_id": deleted["id"],
        "detail": (
            f"Prenotazione eliminata: {deleted['customer_name']} il "
            f"{deleted['reservation_date']} alle {deleted['start_time']}."
        ),
    }


@router.post("/reservations/{reservation_id}/reassign", response_model=AssignmentResult)
def reassign_reservation(
    reservation_id: int,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> AssignmentResult:
    reservation = get_reservation_or_404(reservation_id, db, venue_id=current_venue.id)
    candidate = reassign_single_reservation(reservation, db)
    db.commit()
    db.refresh(reservation)
    serialized = serialize_reservation(reservation, db)
    return AssignmentResult(
        reservation_id=reservation.id,
        assigned_table_id=reservation.assigned_table_id,
        assigned_combination_id=reservation.assigned_combination_id,
        assignment_label=candidate.label if candidate else None,
        requires_table_join=serialized.requires_table_join,
        service_summary=serialized.service_summary,
        service_steps=serialized.service_steps,
    )


@router.post("/reservations/recalculate-day")
def recalculate_day(
    payload: RecalculateDayRequest,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> dict:
    reservations = recalculate_day_assignments(payload.reservation_date, current_venue.id, db)
    unassigned = [
        reservation.id
        for reservation in reservations
        if reservation.assigned_table_id is None and reservation.assigned_combination_id is None
    ]
    return {
        "reservation_date": payload.reservation_date,
        "processed": len(reservations),
        "unassigned_reservation_ids": unassigned,
    }


@router.get("/floor-plan", response_model=FloorPlanResponse)
def get_floor_plan(
    date: date = Query(...),
    room_id: int = Query(...),
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> FloorPlanResponse:
    try:
        return build_floor_plan(date, room_id, db, venue_id=current_venue.id)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc


@router.post("/seed-demo")
def seed_demo(db: Session = Depends(get_db)) -> dict:
    return seed_demo_data(db)


@router.get("/whatsapp/status", response_model=WhatsAppStatusResponse)
def whatsapp_status(db: Session = Depends(get_db), current_venue: Venue = Depends(get_current_venue)) -> WhatsAppStatusResponse:
    return get_whatsapp_status(db=db, venue_id=current_venue.id)


@router.post("/whatsapp/validate-config", response_model=WhatsAppConfigValidationResponse)
def whatsapp_validate_config(db: Session = Depends(get_db), current_venue: Venue = Depends(get_current_venue)) -> WhatsAppConfigValidationResponse:
    return validate_whatsapp_config(db, venue_id=current_venue.id)


@router.post("/whatsapp/test-message", response_model=WhatsAppSendTestResponse)
def whatsapp_test_message(
    payload: WhatsAppSendTestRequest,
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> WhatsAppSendTestResponse:
    return send_test_whatsapp_message(payload.to, payload.message, db, venue_id=current_venue.id)


@router.get("/whatsapp/logs", response_model=list[WhatsAppEventLogRead])
def whatsapp_logs(
    limit: int = Query(default=30, ge=1, le=200),
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> list[WhatsAppEventLogRead]:
    return list_whatsapp_logs(db, limit=limit, venue_id=current_venue.id)


@router.get("/whatsapp/conversations", response_model=list[WhatsAppConversationSummaryRead])
def whatsapp_conversations(
    limit: int = Query(default=20, ge=1, le=100),
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> list[WhatsAppConversationSummaryRead]:
    return [
        WhatsAppConversationSummaryRead.model_validate(item)
        for item in list_whatsapp_conversations(db=db, venue_id=current_venue.id, limit=limit)
    ]


@router.get("/whatsapp/conversations/{contact_phone}/turns", response_model=list[WhatsAppAssistantTurnRead])
def whatsapp_conversation_turns(
    contact_phone: str,
    limit: int = Query(default=60, ge=1, le=200),
    db: Session = Depends(get_db),
    current_venue: Venue = Depends(get_current_venue),
) -> list[WhatsAppAssistantTurnRead]:
    turns = list_whatsapp_assistant_turns(
        db=db,
        venue_id=current_venue.id,
        contact_phone=contact_phone,
        limit=limit,
    )
    return list(reversed(turns))


@router.get("/webhooks/whatsapp", include_in_schema=False)
def whatsapp_webhook_verify(request: Request) -> PlainTextResponse:
    challenge = verify_webhook_handshake(
        mode=request.query_params.get("hub.mode"),
        verify_token=request.query_params.get("hub.verify_token"),
        challenge=request.query_params.get("hub.challenge"),
    )
    return PlainTextResponse(content=challenge)


@router.post("/webhooks/whatsapp", response_model=WhatsAppWebhookAck, include_in_schema=False)
async def whatsapp_webhook_receive(request: Request, db: Session = Depends(get_db)) -> WhatsAppWebhookAck:
    body = await request.body()
    validate_webhook_signature(body, request.headers.get("X-Hub-Signature-256"))
    payload = parse_webhook_body(body)
    processed_events = process_webhook_payload(payload, db)
    return WhatsAppWebhookAck(received=True, processed_events=processed_events)
