from fastapi import FastAPI, HTTPException, status, Depends
from sqlalchemy.orm import Session, joinedload
from typing import List, Optional
from datetime import datetime
from database import (
    get_db,
    DBRestaurante, DBSucursal, DBMesa, DBCliente, DBReserva,
    DBConversacion, DBCategoria, DBPlato, DBMenu, DBItemMenu,
    create_db_tables
)
from schemas import (
    ItemMenuResponse, MenuResponse, PlatoResponse, ReservaResponse, RestauranteCreate, RestauranteRead,
    SucursalCreate, SucursalRead,
    MesaCreate, MesaRead,
    ClienteCreate, ClienteRead,
    ReservaCreate, ReservaRead,
    ConversacionCreate, ConversacionRead,
    CategoriaCreate, CategoriaRead,
    PlatoCreate, PlatoRead,
    MenuCreate, MenuRead,
    ItemMenuCreate, ItemMenuRead
)

# ============================================================
# 🚀 INICIALIZACIÓN DE LA API
# ============================================================

app = FastAPI(
    title="API Restaurante - Sistema de Gestión y Administración",
    root_path="/api-restaurante"
)

@app.on_event("startup")
def startup_event():
    create_db_tables()


# ============================================================
# 🏢 RESTAURANTE
# ============================================================

@app.post("/restaurantes", response_model=RestauranteRead, status_code=201)
def create_restaurante(data: RestauranteCreate, db: Session = Depends(get_db)):
    existente = db.query(DBRestaurante).filter(DBRestaurante.ruc == data.ruc).first()
    if existente:
        raise HTTPException(status_code=409, detail="Ya existe un restaurante con ese RUC.")
    obj = DBRestaurante(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj

@app.patch("/restaurantes/{id}", response_model=RestauranteRead)
def update_restaurante(id: int, data: RestauranteCreate, db: Session = Depends(get_db)):
    obj = db.query(DBRestaurante).filter(DBRestaurante.id == id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Restaurante no encontrado.")
    for k, v in data.model_dump().items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj

@app.get("/restaurantes", response_model=List[RestauranteRead])
def list_restaurantes(db: Session = Depends(get_db)):
    return db.query(DBRestaurante).all()


# ============================================================
# 🏪 SUCURSAL
# ============================================================

@app.post("/sucursales", response_model=SucursalRead, status_code=201)
def create_sucursal(data: SucursalCreate, db: Session = Depends(get_db)):
    obj = DBSucursal(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj

@app.patch("/sucursales/{id}", response_model=SucursalRead)
def update_sucursal(id: int, data: SucursalCreate, db: Session = Depends(get_db)):
    obj = db.query(DBSucursal).filter(DBSucursal.id == id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Sucursal no encontrada.")
    for k, v in data.model_dump().items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj

@app.get("/sucursales", response_model=List[SucursalRead])
def list_sucursales(db: Session = Depends(get_db)):
    return db.query(DBSucursal).all()


# ============================================================
# 🍽️ MESA
# ============================================================

@app.post("/mesas", response_model=MesaRead, status_code=201)
def create_mesa(data: MesaCreate, db: Session = Depends(get_db)):
    obj = DBMesa(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj

@app.patch("/mesas/{id}", response_model=MesaRead)
def update_mesa(id: int, data: MesaCreate, db: Session = Depends(get_db)):
    obj = db.query(DBMesa).filter(DBMesa.id == id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Mesa no encontrada.")
    for k, v in data.model_dump().items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj

@app.get("/mesas", response_model=List[MesaRead])
def list_mesas(db: Session = Depends(get_db)):
    return db.query(DBMesa).all()

from fastapi import Query

@app.get("/mesas/disponibles", response_model=List[MesaRead])
def list_mesas_disponibles(
    fecha_hora: datetime = Query(..., description="Fecha y hora deseada para la reserva"),
    db: Session = Depends(get_db)
):

    # 1️⃣ Subconsulta: obtener los IDs de mesas reservadas en esa fecha y hora
    reservas_ocupadas = db.query(DBReserva.id_mesa).filter(
        DBReserva.estado != "CANCELADA",
        DBReserva.fecha_reserva == fecha_hora
    ).subquery()

    # 2️⃣ Consultar mesas disponibles que no estén en las reservas ocupadas
    mesas_disponibles = db.query(DBMesa).filter(
        DBMesa.disponible == True,
        ~DBMesa.id.in_(reservas_ocupadas)
    ).all()

    return mesas_disponibles


# ============================================================
# 🧍‍♂️ CLIENTE
# ============================================================

@app.post("/clientes", response_model=ClienteRead, status_code=201)
def create_cliente(data: ClienteCreate, db: Session = Depends(get_db)):
    existente = db.query(DBCliente).filter(DBCliente.whatsapp == data.whatsapp).first()
    if existente:
        raise HTTPException(status_code=409, detail="Ya existe un cliente con ese WhatsApp.")
    obj = DBCliente(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj

@app.patch("/clientes/{id}", response_model=ClienteRead)
def update_cliente(id: int, data: ClienteCreate, db: Session = Depends(get_db)):
    obj = db.query(DBCliente).filter(DBCliente.id == id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Cliente no encontrado.")
    for k, v in data.model_dump().items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj

@app.get("/clientes", response_model=List[ClienteRead])
def list_clientes(db: Session = Depends(get_db)):
    return db.query(DBCliente).all()

@app.get("/clientes/whatsapp/{whatsapp}", response_model=ClienteRead)
def get_cliente_by_whatsapp(whatsapp: str, db: Session = Depends(get_db)):
    cliente = db.query(DBCliente).filter(DBCliente.whatsapp == whatsapp).first()

    if not cliente:
        raise HTTPException(
            status_code=404,
            detail=f"Cliente con WhatsApp '{whatsapp}' no encontrado."
        )

    # ✅ Conversión segura al modelo Pydantic (v2 compatible)
    return ClienteRead.model_validate(cliente, from_attributes=True)

# ============================================================
# 📅 RESERVA
# ============================================================

@app.post("/reservas", response_model=ReservaResponse, status_code=201)
def create_reserva(data: ReservaCreate, db: Session = Depends(get_db)):
    # 1. Verificar que la mesa exista
    mesa = db.query(DBMesa).filter(DBMesa.id == data.id_mesa).first()
    if not mesa:
        raise HTTPException(status_code=404, detail="Mesa no encontrada.")

    # 2. Crear la reserva
    obj = DBReserva(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)

    # 3. Retornar la reserva con detalles de mesa
    return ReservaResponse(
        id=obj.id,
        mesa=f"Mesa {mesa.identificador_mesa}",
        ubicacion=mesa.ubicacion,
        fecha_reserva=obj.fecha_reserva,
        estado=obj.estado,
        fecha_registro=obj.fecha_registro
    )

@app.patch("/reservas/{id}", response_model=ReservaRead)
def update_reserva(id: int, data: ReservaCreate, db: Session = Depends(get_db)):
    obj = db.query(DBReserva).filter(DBReserva.id == id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Reserva no encontrada.")
    for k, v in data.model_dump().items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj

@app.post("/reservas", response_model=ReservaResponse, status_code=201)
def create_reserva(data: ReservaCreate, db: Session = Depends(get_db)):
    # 1. Verificar que la mesa exista
    mesa = db.query(DBMesa).filter(DBMesa.id == data.id_mesa).first()
    if not mesa:
        raise HTTPException(status_code=404, detail="Mesa no encontrada.")

    # 2. Crear la reserva
    obj = DBReserva(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)

    # 3. Retornar la reserva con detalles de mesa
    return ReservaResponse(
        id=obj.id,
        mesa=f"Mesa {mesa.identificador_mesa}",
        ubicacion=mesa.ubicacion,
        fecha_reserva=obj.fecha_reserva,
        estado=obj.estado,
        fecha_registro=obj.fecha_registro
    )


@app.get("/reservas", response_model=List[ReservaRead])
def list_reservas(db: Session = Depends(get_db)):
    """Lista todas las reservas"""
    return db.query(DBReserva).all()


@app.get("/reservas/cliente", response_model=List[ReservaResponse])
def listar_reservas_cliente(
    cliente_id: int = Query(..., description="ID del cliente"),
    db: Session = Depends(get_db)
):
    """
    Lista solo las reservas PENDIENTES de un cliente.
    Incluye el identificador y la ubicación de la mesa por separado.
    """
    reservas = (
        db.query(DBReserva)
        .join(DBMesa, DBReserva.id_mesa == DBMesa.id)
        .filter(DBReserva.id_cliente == cliente_id)
        .filter(DBReserva.estado == "PENDIENTE")
        .add_columns(DBMesa.identificador_mesa, DBMesa.ubicacion)
        .all()
    )

    respuesta = [
        ReservaResponse(
            id=r.DBReserva.id,
            mesa=f"Mesa {r.identificador_mesa}",
            ubicacion=r.ubicacion,
            fecha_reserva=r.DBReserva.fecha_reserva,
            estado=r.DBReserva.estado,
            fecha_registro=r.DBReserva.fecha_registro
        )
        for r in reservas
    ]

    return respuesta

@app.patch("/reservas/cancelar/{reserva_id}", response_model=ReservaResponse)
def cancelar_reserva(reserva_id: int, db: Session = Depends(get_db)):
    reserva = db.query(DBReserva).filter(DBReserva.id == reserva_id).first()
    if not reserva:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Reserva no encontrada")
    
    if reserva.estado == "CANCELADA":
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="La reserva ya está cancelada")

    # 1. Cambiar el estado de la reserva
    reserva.estado = "CANCELADA"
    db.commit()
    db.refresh(reserva)

    # 2. Obtener los datos de la mesa
    mesa = db.query(DBMesa).filter(DBMesa.id == reserva.id_mesa).first()

    # 3. Retornar respuesta detallada
    return ReservaResponse(
        id=reserva.id,
        mesa=f"Mesa {mesa.identificador_mesa}",
        ubicacion=mesa.ubicacion,
        fecha_reserva=reserva.fecha_reserva,
        estado=reserva.estado,
        fecha_registro=reserva.fecha_registro
    )


# ============================================================
# 💬 CONVERSACION
# ============================================================

@app.post("/conversaciones", response_model=ConversacionRead, status_code=201)
def create_conversacion(data: ConversacionCreate, db: Session = Depends(get_db)):
    if data.wamid:
        existente = db.query(DBConversacion).filter(DBConversacion.wamid == data.wamid).first()
        if existente:
            raise HTTPException(status_code=409, detail="Ya existe una conversación con ese wamid.")
    obj = DBConversacion(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj

@app.patch("/conversaciones/{id}", response_model=ConversacionRead)
def update_conversacion(id: int, data: ConversacionCreate, db: Session = Depends(get_db)):
    obj = db.query(DBConversacion).filter(DBConversacion.id == id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Conversación no encontrada.")
    for k, v in data.model_dump().items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj

@app.get("/conversaciones", response_model=List[ConversacionRead])
def list_conversaciones(db: Session = Depends(get_db)):
    return db.query(DBConversacion).all()


# ============================================================
# 🏷️ CATEGORIA
# ============================================================

@app.post("/categorias", response_model=CategoriaRead, status_code=201)
def create_categoria(data: CategoriaCreate, db: Session = Depends(get_db)):
    obj = DBCategoria(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj

@app.patch("/categorias/{id}", response_model=CategoriaRead)
def update_categoria(id: int, data: CategoriaCreate, db: Session = Depends(get_db)):
    obj = db.query(DBCategoria).filter(DBCategoria.id == id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Categoría no encontrada.")
    for k, v in data.model_dump().items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj

@app.get("/categorias", response_model=List[CategoriaRead])
def list_categorias(db: Session = Depends(get_db)):
    return db.query(DBCategoria).all()


# ============================================================
# 🥘 PLATO
# ============================================================

@app.post("/platos", response_model=PlatoRead, status_code=201)
def create_plato(data: PlatoCreate, db: Session = Depends(get_db)):
    obj = DBPlato(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj

@app.patch("/platos/{id}", response_model=PlatoRead)
def update_plato(id: int, data: PlatoCreate, db: Session = Depends(get_db)):
    obj = db.query(DBPlato).filter(DBPlato.id == id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Plato no encontrado.")
    for k, v in data.model_dump().items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj

@app.get("/platos", response_model=List[PlatoResponse]) 
def list_platos(db: Session = Depends(get_db)):
    """
    Lista todos los platos disponibles, incluyendo el nombre de su categoría.
    """
    
    # 1. Consulta SQL con JOIN
    platos_con_categoria = db.query(
        DBPlato.id,
        DBPlato.nombre,
        DBPlato.precio_base,
        DBCategoria.nombre.label('categoria') # Alias 'categoria'
    ).join(DBCategoria, DBPlato.id_categoria == DBCategoria.id).all()

    # 2. Mapeo al DTO de Respuesta (PlatoResponse)
    return [
        PlatoResponse(
            id=p.id,
            nombre=p.nombre,
            categoria=p.categoria, # Usa el alias 'categoria' de la consulta
            precio_base=p.precio_base
        )
        for p in platos_con_categoria
    ]


# ============================================================
# 📜 MENU
# ============================================================

@app.post("/menus", response_model=MenuRead, status_code=201)
def create_menu(data: MenuCreate, db: Session = Depends(get_db)):
    obj = DBMenu(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj

@app.patch("/menus/{id}", response_model=MenuRead)
def update_menu(id: int, data: MenuCreate, db: Session = Depends(get_db)):
    obj = db.query(DBMenu).filter(DBMenu.id == id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Menú no encontrado.")
    for k, v in data.model_dump().items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj

@app.get("/menus", response_model=List[MenuRead])
def list_menus(db: Session = Depends(get_db)):
    # Devuelve todos los menús
    return db.query(DBMenu).all()

from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder

@app.get("/menus/fecha", response_model=List[MenuResponse])
def list_menus_by_date(
    target_datetime: Optional[datetime] = Query(None, description="Fecha y hora deseada para verificar la vigencia del menú"),
    db: Session = Depends(get_db)
):
    """
    Lista los menús cuya vigencia incluye una fecha y hora específicas.
    Si no se proporciona target_datetime, devuelve todos los menús.
    """

    query = db.query(DBMenu)

    if target_datetime:
        query = query.filter(
            DBMenu.inicio <= target_datetime,
            DBMenu.fin >= target_datetime
        )

    menus_db = query.options(
        joinedload(DBMenu.items).joinedload(DBItemMenu.plato).joinedload(DBPlato.categoria)
    ).all()

    response: List[MenuResponse] = []

    for menu in menus_db:
        items_response: List[ItemMenuResponse] = []
        for item in menu.items:
            plato = item.plato
            if plato and plato.categoria:
                items_response.append(ItemMenuResponse(
                    nombre_plato=plato.nombre,
                    categoria=plato.categoria.nombre,
                    precio_base=plato.precio_base,
                    precio_especial=item.precio_especial
                ))

        response.append(MenuResponse(
            id=menu.id,
            nombre=menu.nombre,
            inicio=menu.inicio,
            fin=menu.fin,
            items=items_response
        ))

    # ✅ Convertimos todo a JSON serializable (formato ISO para las fechas)
    json_data = jsonable_encoder(response)

    # ✅ Enviamos la respuesta en formato JSON limpio
    return JSONResponse(content=json_data)


# ============================================================
# 📝 ITEM MENU
# ============================================================

@app.post("/items-menu", response_model=ItemMenuRead, status_code=201)
def create_item_menu(data: ItemMenuCreate, db: Session = Depends(get_db)):
    obj = DBItemMenu(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj

@app.patch("/items-menu/{id}", response_model=ItemMenuRead)
def update_item_menu(id: int, data: ItemMenuCreate, db: Session = Depends(get_db)):
    obj = db.query(DBItemMenu).filter(DBItemMenu.id == id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Item del menú no encontrado.")
    for k, v in data.model_dump().items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj

@app.get("/items-menu", response_model=List[ItemMenuRead])
def list_items_menu(db: Session = Depends(get_db)):
    return db.query(DBItemMenu).all()
