import os
from typing import List, Any, Optional, Dict
from fastapi.responses import HTMLResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware

from pymongo import MongoClient
from bson.objectid import ObjectId
from bson.errors import InvalidId
from dotenv import load_dotenv
from pydantic import BaseModel, Field, ConfigDict
from pymongo.errors import ServerSelectionTimeoutError
from fastapi import FastAPI, HTTPException, status, APIRouter, Query
from datetime import datetime, timedelta

import uvicorn

load_dotenv()

# --- CONFIGURACIÓN REGIONAL Y AYUDAS ---
DAY_MAP = {
    0: 'Lunes', 1: 'Martes', 2: 'Miércoles', 3: 'Jueves',
    4: 'Viernes', 5: 'Sábado', 6: 'Domingo'
}

def get_day_name_in_spanish(date_str: str) -> str:
    try:
        dt = datetime.strptime(date_str, "%Y-%m-%d")
        return DAY_MAP[dt.weekday()]
    except ValueError:
        raise HTTPException(status_code=400, detail="Formato de fecha inválido. Use YYYY-MM-DD.")
    
def get_end_time(start_time_str: str) -> str:
    try:
        start_time = datetime.strptime(start_time_str, "%H:%M")
        end_time = start_time + timedelta(hours=1)
        return end_time.strftime("%H:%M")
    except ValueError:
        raise HTTPException(status_code=400, detail="Formato de hora inválido. Use HH:MM.")

# --- MODELOS BASE DE MONGO ---
class MongoBaseModel(BaseModel):
    id: Optional[str] = Field(alias="_id", default=None)
    model_config = ConfigDict(
        populate_by_name=True,
        json_schema_extra={"example": {"id": "60d5ec49b14f6b3d4f4d4e3a"}}
    )

# ======================================================================
# --- MODELOS DE RESPUESTA PARA EL AGENTE (MCP) ---
# ======================================================================

class DisponibilidadClub(BaseModel):
    id_club: str = Field(..., description="ID del club.")
    nombre: str = Field(..., description="Nombre del club.")
    horasDisponibles: List[str] = Field(..., description="Lista de horas de inicio disponibles (HH:MM).")
    precio_minimo: float = Field(..., description="Precio más bajo de cancha disponible.")

class CanchaDisponible(BaseModel):
    id_club: str = Field(..., description="ID del club.")
    cancha_id_local: str = Field(..., description="ID local de la cancha (e.g., C1, Padel_A).")
    nombre_cancha: str = Field(..., description="Nombre amigable de la cancha.")
    precio_hora: float = Field(..., gt=0, description="Precio por una hora en esta cancha específica.")

class RespuestaCancelacion(BaseModel):
    estado: str = Field(..., description="Estado de la reserva tras la acción (ej: 'Cancelada').")
    mensaje: str = Field(..., description="Mensaje informativo para el usuario.")

# ======================================================================
# --- MODELOS DE LIA SPORTS (ESTRUCTURA ANIDADA) ---
# ======================================================================

class DisponibilidadHoras(BaseModel):
    horas: List[str] = Field(..., description="Lista de horas de inicio disponibles (HH:MM).")

class DisponibilidadDia(BaseModel):
    dia_semana: str = Field(..., description="Día al que aplica (ej: 'Lunes', 'Sábado').")
    horarios: DisponibilidadHoras

class CanchaDetalle(BaseModel):
    cancha_id_local: str = Field(..., description="ID local de la cancha (e.g., C1, Padel_A).")
    nombre_cancha: str = Field(..., description="Nombre amigable de la cancha.")
    tipo_deporte: str = Field("Padel", description="Deporte que se practica aquí.")
    precio_hora_base: float = Field(..., gt=0, description="Precio base por una hora de reserva.")

class Club(MongoBaseModel):
    nombre: str = Field(..., description="Nombre del club o instalación.")
    direccion: str = Field(..., description="Dirección física del club.")
    disponibilidad_semanal: List[DisponibilidadDia] = Field(default_factory=list, description="Horarios operativos disponibles por día.")
    canchas: List[CanchaDetalle] = Field(default_factory=list, description="Lista de canchas disponibles en este club.")

class Cliente(MongoBaseModel):
    nombre: str = Field(..., description="Nombre completo del cliente.")
    telefono: str = Field(..., description="Teléfono de contacto (para identificación).") 

class Reserva(MongoBaseModel):
    id_club: str = Field(..., description="ID del Club (FK a clubs).")
    cancha_id_local: str = Field(..., description="ID local de la Cancha dentro del Club.")
    id_cliente: str = Field(..., description="ID del Cliente que realiza la reserva.")
    fecha: str = Field(..., description="Fecha de la reserva (YYYY-MM-DD).")
    hora_inicio: str = Field(..., description="Hora de inicio (HH:MM).")
    hora_fin: str = Field(..., description="Hora de fin (HH:MM).")
    estado: str = Field("Confirmada", description="Estado de la reserva (Confirmada, Cancelada).")

# --- CONFIGURACIÓN DE FASTAPI Y CONEXIÓN A MONGO ---
app = FastAPI(title="API LIA Sports - MongoDB", root_path="/lhia-sport")

# CORS (opcional pero útil para frontend)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

router = APIRouter() 

MONGO_URI = os.getenv("MONGO_URI")
MONGO_DB_NAME = os.getenv("MONGO_DB_NAME")

client: MongoClient = None
db: Any = None

# Endpoints que sirven el layout (SPA) — IMPORTANT: ambos devuelven layout.html
@app.get("/", response_class=HTMLResponse, include_in_schema=False, tags=["Frontend"])
async def serve_main_frontend():
    frontend_path = "static/layout/layout.html"
    if not os.path.exists(frontend_path):
        raise HTTPException(status_code=404, detail="Frontend (HTML) no encontrado.")
    return FileResponse(frontend_path, media_type="text/html")

@app.get("/admin/", response_class=HTMLResponse, include_in_schema=False, tags=["Frontend"])
async def serve_admin_panel():
    # En lugar de devolver el fragmento admin_panel.html devolvemos el layout completo.
    frontend_path = "static/layout/layout.html"
    if not os.path.exists(frontend_path):
        raise HTTPException(status_code=404, detail="Frontend (HTML) no encontrado.")
    return FileResponse(frontend_path, media_type="text/html")

@app.get("/admin/reservas/", response_class=HTMLResponse, include_in_schema=False, tags=["Frontend"])
async def serve_admin_panel():
    # En lugar de devolver el fragmento admin_panel.html devolvemos el layout completo.
    frontend_path = "static/admin_panel/reservas_tab.html"
    if not os.path.exists(frontend_path):
        raise HTTPException(status_code=404, detail="Frontend (HTML) no encontrado.")
    return FileResponse(frontend_path, media_type="text/html")

@app.on_event("startup")
def startup_db_client():
    global client, db
    
    if not MONGO_URI or not MONGO_DB_NAME:
        raise ValueError("MONGO_URI y MONGO_DB_NAME deben estar definidos.")
    try:
        client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=10000) 
        client.admin.command('ping') 
        db = client[MONGO_DB_NAME]
    except (ServerSelectionTimeoutError, Exception): 
        db = None 

@app.on_event("shutdown")
def shutdown_db_client():
    global client
    if client:
        client.close()

# --- HELPERS ---
def validate_object_id(id: str):
    try:
        return ObjectId(id)
    except Exception:
        raise HTTPException(status_code=400, detail="ID inválido")

def get_collection(name: str):
    global db
    if db is None:
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Error: La conexión a DB no está activa.")
    return db[name]

def insert_and_return(collection_name: str, data: dict, model: BaseModel):
    collection = get_collection(collection_name)
    if 'id' in data: data.pop("id")
    
    try:
        result = collection.insert_one(data)
    except Exception as e:
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error DB al crear {collection_name}: {e}")
        
    created = collection.find_one({"_id": result.inserted_id})
    if created and created.get('_id'): created['_id'] = str(created['_id'])
    return model.model_validate(created, from_attributes=True)

def find_all_and_validate(collection_name: str, model: BaseModel) -> List[BaseModel]:
    collection = get_collection(collection_name)
    
    try:
        results = collection.find()
        validated_results = []
        for doc in results:
            if doc.get('_id'): doc['_id'] = str(doc['_id'])
            validated_results.append(model.model_validate(doc, from_attributes=True))
        return validated_results
    except Exception as e:
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error DB al listar {collection_name}: {e}")

# ======================================================================
# --- ENDPOINTS: CLUB (Crear y Listar) ---
# ======================================================================

@router.post("/clubs/", response_model=Club, status_code=status.HTTP_201_CREATED, tags=["Clubes"])
def create_club(club_data: Club):
    return insert_and_return("clubs", club_data.model_dump(by_alias=True, exclude_none=True), Club)

@router.get("/clubs/", response_model=List[Club], tags=["Clubes"])
def read_clubs():
    return find_all_and_validate("clubs", Club)

# ======================================================================
# --- ENDPOINTS: DISPONIBILIDAD ---
# ======================================================================

@router.get("/clubs/availability/{fecha}", response_model=List[DisponibilidadClub], tags=["Disponibilidad"])
def encontrar_bloques_disponibles(fecha: str):
    dia_semana = get_day_name_in_spanish(fecha)
    reservas_collection = get_collection("reservas")
    
    filtro_reservas = {
        "fecha": fecha,
        "estado": "Confirmada" 
    }
    
    reservas_existentes = reservas_collection.find(filtro_reservas)
    
    reservas_map: Dict[tuple, List[str]] = {}
    for res in reservas_existentes:
        key = (res['id_club'], res['cancha_id_local'])
        if key not in reservas_map:
            reservas_map[key] = []
        reservas_map[key].append(res['hora_inicio'])
        
    clubs_collection = get_collection("clubs")
    todos_los_clubs = clubs_collection.find()
    
    disponibilidad_final: List[DisponibilidadClub] = []
    
    for club_doc in todos_los_clubs:
        if club_doc.get('_id'):
            club_doc['_id'] = str(club_doc['_id'])
        club = Club.model_validate(club_doc, from_attributes=True)
        club_id = str(club.id)
        
        horario_dia = next((d.horarios.horas for d in club.disponibilidad_semanal if d.dia_semana == dia_semana), [])
        if not horario_dia:
            continue
            
        horas_disponibles_club: set[str] = set()
        min_price = float('inf')
        
        for hora_inicio in horario_dia:
            cancha_libre_en_hora = False
            for cancha in club.canchas:
                key = (club_id, cancha.cancha_id_local)
                horas_reservadas_cancha = reservas_map.get(key, [])
                if hora_inicio not in horas_reservadas_cancha:
                    cancha_libre_en_hora = True
                    min_price = min(min_price, cancha.precio_hora_base)
                    break
            if cancha_libre_en_hora:
                horas_disponibles_club.add(hora_inicio)
                
        if horas_disponibles_club:
            disponibilidad_final.append(
                DisponibilidadClub(
                    id_club=club_id,
                    nombre=club.nombre,
                    horasDisponibles=sorted(list(horas_disponibles_club)),
                    precio_minimo=min_price if min_price != float('inf') else 0.0
                )
            )
    return disponibilidad_final

@router.get("/clubs/{id_club}/canchas/availability", response_model=List[CanchaDisponible], tags=["Disponibilidad"])
def verificar_disponibilidad(
    id_club: str, 
    fecha: str = Query(..., description="Fecha de reserva (YYYY-MM-DD)"),
    hora_inicio: str = Query(..., description="Hora de inicio (HH:MM)")
):
    club_id_obj = validate_object_id(id_club)
    get_end_time(hora_inicio)
    
    clubs_collection = get_collection("clubs")
    club_doc = clubs_collection.find_one({"_id": club_id_obj})
    
    if not club_doc:
        raise HTTPException(status_code=404, detail=f"Club con ID {id_club} no encontrado.")
        
    club_doc['_id'] = str(club_doc['_id'])
    club = Club.model_validate(club_doc, from_attributes=True)
    
    reservas_collection = get_collection("reservas")
    reservas_ocupadas = reservas_collection.find({
        "id_club": id_club,
        "fecha": fecha,
        "hora_inicio": hora_inicio,
        "estado": "Confirmada"
    })
    
    canchas_ocupadas = {res['cancha_id_local'] for res in reservas_ocupadas}
    
    canchas_disponibles: List[CanchaDisponible] = []
    
    for cancha in club.canchas:
        if cancha.cancha_id_local not in canchas_ocupadas:
            canchas_disponibles.append(
                CanchaDisponible(
                    id_club=id_club,
                    cancha_id_local=cancha.cancha_id_local,
                    nombre_cancha=cancha.nombre_cancha,
                    precio_hora=cancha.precio_hora_base
                )
            )
    return canchas_disponibles

# ======================================================================
# --- ENDPOINTS: CLIENTE (Crear y Listar) ---
# ======================================================================

@router.post("/clientes/", response_model=Cliente, status_code=status.HTTP_201_CREATED, tags=["Clientes"])
def create_cliente(cliente_data: Cliente):
    return insert_and_return("clientes", cliente_data.model_dump(by_alias=True, exclude_none=True), Cliente)

@router.get("/clientes/", response_model=List[Cliente], tags=["Clientes"])
def read_clientes():
    return find_all_and_validate("clientes", Cliente)

# ======================================================================
# --- ENDPOINTS: RESERVA ---
# ======================================================================

@router.post("/reservas/", response_model=Reserva, status_code=status.HTTP_201_CREATED, tags=["Reservas"])
def create_reserva(reserva_data: Reserva):
    reserva_data.hora_fin = get_end_time(reserva_data.hora_inicio)
    return insert_and_return("reservas", reserva_data.model_dump(by_alias=True, exclude_none=True), Reserva)

@router.get("/reservas/", response_model=List[Reserva], tags=["Reservas"])
def read_reservas():
    return find_all_and_validate("reservas", Reserva)

@router.get("/reservas/cliente/{identificadorUsuario}", response_model=List[Reserva], tags=["Reservas"])
def ver_reservas_cliente(identificadorUsuario: str):
    reservas_collection = get_collection("reservas")
    clientes_collection = get_collection("clientes")
    
    candidatos_id_cliente = {identificadorUsuario}
    cliente_doc = clientes_collection.find_one({"telefono": identificadorUsuario})
    if cliente_doc:
        candidatos_id_cliente.add(str(cliente_doc['_id']))

    filtro_reservas = {
        "estado": "Confirmada",
        "id_cliente": {"$in": list(candidatos_id_cliente)}
    }

    results = reservas_collection.find(filtro_reservas)
    validated_results = []
    for doc in results:
        if doc.get('_id'): doc['_id'] = str(doc['_id'])
        validated_results.append(Reserva.model_validate(doc, from_attributes=True))
    return validated_results

@router.delete("/reservas/{id_reserva}", response_model=RespuestaCancelacion, tags=["Reservas"])
def cancelar_reserva(
    id_reserva: str, 
    userIdentifier: str = Query(..., description="Identificador (ID de cliente o teléfono) para validar la cancelación.")
):
    try:
        reserva_id_obj = ObjectId(id_reserva)
    except InvalidId:
        raise HTTPException(status_code=400, detail="El ID de reserva proporcionado no es un formato válido.")

    reservas_collection = get_collection("reservas")
    reserva_doc = reservas_collection.find_one({"_id": reserva_id_obj})
    if not reserva_doc:
        raise HTTPException(status_code=404, detail="Reserva no encontrada.")
    
    try:
        reserva_doc['_id'] = str(reserva_doc['_id'])
        reserva = Reserva.model_validate(reserva_doc, from_attributes=True)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error interno al validar la reserva: {str(e)}")
    
    if reserva.id_cliente != userIdentifier:
        raise HTTPException(status_code=403, detail="No estás autorizado para cancelar esta reserva. Solo el propietario puede hacerlo.")
        
    if reserva.estado == "Cancelada":
        raise HTTPException(status_code=400, detail="Esta reserva ya estaba cancelada.")
        
    result = reservas_collection.update_one(
        {"_id": reserva_id_obj, "id_cliente": userIdentifier},
        {"$set": {"estado": "Cancelada"}}
    )
    
    if result.modified_count == 1:
        return RespuestaCancelacion(
            estado="Cancelada", 
            mensaje=f"La reserva {id_reserva} ha sido cancelada exitosamente."
        )
    raise HTTPException(status_code=500, detail="No se pudo actualizar el estado de la reserva. Contacta a soporte.")

# Incluir router y montar estáticos
app.include_router(router)
app.mount("/static", StaticFiles(directory="static"), name="static")

if __name__ == "__main__":
    uvicorn.run(
        "fast_api_canchas:app",
        host="0.0.0.0",
        port=8012,
        reload=True,
        root_path="/lhia-sport"
    )