""" Event-Service relationship CRUD operations """
from typing import List, Optional
from uuid import UUID
from sqlmodel import Session, select, func
from fastapi import HTTPException
from datetime import datetime, timedelta, timezone
from typing import Tuple

from app.models import (
    EventService,
    EventServiceOut,
    EventServicesOut,
    Event,
    Service,
    EventAllServicesOut,
    EventServiceUpdate,
    ServiceType,
    AvailabilityRule,
    EspaiPropi,
    EventData
)
from app.crud.service import _prepare_service_dict

def calcular_cantidad_por_tipo(tipo: str, num_personas: int) -> int:
    tipo = tipo.upper()
    print(f"Calculando cantidad por tipo: {tipo}")
    if tipo == "SILLAS_TABURETES":
        return num_personas
    elif tipo == "FUNDAS_SILLA":
        return num_personas
    elif tipo == "VASOS":
        return num_personas
    elif tipo == "VAJILLA_BANDEJAS":
        return num_personas
    elif tipo == "MANTELES_SERVILLETAS":
        return num_personas
    elif tipo == "MESAS":
        return (num_personas + 7) // 8  # 1 mesa cada 8 personas
    elif tipo == "TRONAS":
        return num_personas
    elif tipo == "DISFRACES":
        return num_personas // 3
    elif tipo in ["BUFET", "BARBACOA", "FOODTRUCK"]:
        return max(1, num_personas // 40)
    elif tipo in ["ESTUFAS_CALEFACTORES", "VENTILADORES_AIRE"]:
        return max(1, num_personas // 25)
    elif tipo in ["CARPAS", "WC", "COLGADORES_GUARDARROPAS"]:
        return max(1, num_personas // 30)
    elif tipo == "LUCES_GUINALDA":
        return max(1, num_personas // 5)
    elif tipo == "FLORES":
        return max(1, num_personas // 16)
    elif tipo == "GLOBOS":
        return max(1, num_personas // 2)
    elif tipo in ["PHOTOCALL", "PIZARRAS", "ALTAVOCES", "MICROFONOS"]:
        return 1
    elif tipo == "CONTENEDORES_PAPELERAS":
        return max(1, num_personas // 30)
    else:
        return 1  # Tipo desconocido o no cuantificable


def calcular_disponibilidad_reducida(session: Session, service_data: dict, tipo: str, event_data: EventData, time_change: int):
    if not _validate_service_availability(
        session=session,
        service_id=service_data['id'],
        data_inicio=event_data.data_inici,
        data_fin=event_data.data_fi
    ):
        if tipo != "espai":
            if not _validate_service_availability(
                session=session,
                service_id=service_data['id'],
                data_inicio=event_data.data_inici + timedelta(hours=time_change),
                data_fin=event_data.data_fi - timedelta(hours=time_change)
            ):
                if not _validate_service_availability(
                session=session,
                service_id=service_data['id'],
                data_inicio=event_data.data_inici + timedelta(hours=time_change*2),
                data_fin=event_data.data_fi
                ):
                    if not _validate_service_availability(
                        session=session,
                        service_id=service_data['id'],
                        data_inicio=event_data.data_inici,
                        data_fin=event_data.data_fi - timedelta(hours=time_change*2)
                    ):
                        return False, None
                    return True, 2
                return True, 1
            return True, 0
    return True, -1


def calcular_disponibilidad_reducida_2(session: Session, service_data: Service, tipo: str, event_data: EventData, time_change: int):
    if not _validate_service_availability(
        session=session,
        service_id=service_data.id,
        data_inicio=event_data.data_inici,
        data_fin=event_data.data_fi
    ):
        if tipo != "espai":
            if not _validate_service_availability(
                session=session,
                service_id=service_data.id,
                data_inicio=event_data.data_inici + timedelta(hours=time_change),
                data_fin=event_data.data_fi - timedelta(hours=time_change)
            ):
                if not _validate_service_availability(
                session=session,
                service_id=service_data.id,
                data_inicio=event_data.data_inici + timedelta(hours=time_change*2),
                data_fin=event_data.data_fi
                ):
                    if not _validate_service_availability(
                        session=session,
                        service_id=service_data.id,
                        data_inicio=event_data.data_inici,
                        data_fin=event_data.data_fi - timedelta(hours=time_change*2)
                    ):
                        return False, None
                    return True, 2
                return True, 1
            return True, 0
    return True, -1

def is_time_in_range(start, end, check_start, check_end):
    """
    Devuelve True si el rango [check_start, check_end] está completamente dentro de [start, end],
    teniendo en cuenta si el rango cruza la medianoche.
    """
    if start <= end:
        # Rango normal
        return start <= check_start and check_end <= end
    else:
        # Rango que cruza la medianoche
        # El evento debe cruzar la medianoche (check_start > check_end)
        # Y debe empezar después de start O terminar antes de end
        return (
        (check_start >= start or check_end <= end)  # contenido en los extremos
        and (
            check_start > check_end                 # el rango de comprobación también cruza
            or check_start <= check_end            # o está dentro del rango cruzado sin cruzar
        )
    )


def _get_service_price(
    *,
    session: Session,
    service_id: UUID,
    data_inicio: datetime,
    data_fin: datetime
) -> float:
    """Get the price for a service based on its availability rules"""
    # Get all availability rules for the service
    rules = session.exec(
        select(AvailabilityRule).where(AvailabilityRule.service_id == service_id)
    ).all()
    
    if not rules:
        raise HTTPException(status_code=400, detail="No availability rules found for this service")
    
    # Get event times
    event_start_time = data_inicio.time()
    event_end_time = data_fin.time()
    
    # Get the service type
    service = session.get(Service, service_id)
    if not service:
        raise HTTPException(status_code=404, detail="Service not found")
    
    # Find the matching rule
    for rule in rules:
        # Check if the event day is in the rule's days
        if data_inicio.weekday() + 1 in rule.dias_semana:  # +1 because weekday() returns 0-6
            # Check if the event time range is within the rule's time range
            if is_time_in_range(rule.hora_inicio, rule.hora_fin, event_start_time, event_end_time):
                price = rule.price
                # Si es espai, fotografia o musica, multiplicar por horas
                if service.type in [ServiceType.ESPAI, ServiceType.FOTOGRAFIA, ServiceType.MUSICA]:
                    duration = data_fin - data_inicio
                    hours = duration.total_seconds() / 3600
                    return price * hours
                # En el resto de casos, el precio es el de la regla
                return price
    
    raise HTTPException(status_code=400, detail="No matching availability rule found for the specified time range")

def _update_event_budget(session: Session, event_id: UUID) -> None:
    """Update event budget based on services"""
    # Get all services for the event with their quantities
    statement = select(EventService, Service).join(
        Service, EventService.service_id == Service.id
    ).where(EventService.event_id == event_id)
    
    results = session.exec(statement).all()
    
    # Calculate total budget
    total_budget = 0
    for event_service, service in results:
        price = _get_service_price(
            session=session,
            service_id=service.id,
            data_inicio=event_service.data_inicio,
            data_fin=event_service.data_fin
        )
        total_budget += event_service.cantidad * price
    
    # Update event budget
    event = session.get(Event, event_id)
    if event:
        event.pressupost = total_budget
        session.add(event)
        session.commit()

def _validate_service_availability(
    *,
    session: Session,
    service_id: UUID,
    data_inicio: datetime,
    data_fin: datetime
) -> bool:
    """Validate if a service is available for the given time range"""
    # Get the service
    service = session.get(Service, service_id)
    if not service:
        raise HTTPException(status_code=404, detail="Service not found")
    
    # Get all availability rules for the service
    rules = session.exec(
        select(AvailabilityRule).where(AvailabilityRule.service_id == service_id)
    ).all()
    
    if not rules:
        return True  # No rules means always available

    # Check if the event time range falls within any availability rule
    for rule in rules:
        # Get event times
        event_start_time = data_inicio.time()
        event_end_time = data_fin.time()
        rule_start = rule.hora_inicio
        rule_end = rule.hora_fin
        
        # Check if the event day is in the rule's days
        if data_inicio.weekday() + 1 in rule.dias_semana:  # +1 because weekday() returns 0-6
            if is_time_in_range(rule_start, rule_end, event_start_time, event_end_time):
                return True
    
    return False

def _validate_service_capacity(
    *,
    session: Session,
    service_id: UUID,
    data_inicio: datetime,
    data_fin: datetime,
    cantidad: int = 1,
    exclude_event_service_id: Optional[UUID] = None
) -> bool:
    """Validate if a service has enough capacity for the given time range"""
    # Get the service
    service = session.get(Service, service_id)
    if not service:
        raise HTTPException(status_code=404, detail="Service not found")
    
    # Get all existing services for the time range
    query = select(EventService).join(Service).where(
        Service.id == service_id,
        EventService.data_inicio <= data_fin,
        EventService.data_fin >= data_inicio
    )
    
    # Exclude the current event_service if provided
    if exclude_event_service_id:
        query = query.where(EventService.id != exclude_event_service_id)
    
    existing_services = session.exec(query).all()
    
    # Check capacity based on service type
    if service.type == ServiceType.MUSICA or service.type == ServiceType.FOTOGRAFIA:
        # Only one service at a time
        return len(existing_services) == 0
    
    elif service.type == ServiceType.ESPAI:
        # Only one service at a time
        return len(existing_services) == 0
    
    elif service.type == ServiceType.CATERING:
        # Check total comensals
        total_comensals = sum(es.cantidad for es in existing_services)
        if hasattr(service, 'catering') and service.catering:
            max_comensals = service.catering.max_comensals if hasattr(service.catering, 'max_comensals') else float('inf')
            return total_comensals + cantidad <= max_comensals
        return True
    
    elif service.type == ServiceType.MOBILIARI_DECORACIO:
        # Check total quantity
        total_quantity = sum(es.cantidad for es in existing_services)
        if hasattr(service, 'mobiliari_decoracio') and service.mobiliari_decoracio:
            cantidad_max = service.mobiliari_decoracio.cantidad_max if hasattr(service.mobiliari_decoracio, 'cantidad_max') else float('inf')
            return total_quantity + cantidad <= cantidad_max
        return True
    
    return True

def ensure_aware_utc(dt):
    if dt is None:
        return None
    if dt.tzinfo is None:
        return dt.replace(tzinfo=timezone.utc)
    return dt.astimezone(timezone.utc)

def create_event_service(
    *,
    session: Session,
    event_id: UUID,
    service_id: UUID,
    cantidad: int = 1,
    data_inicio: datetime,
    data_fin: datetime
) -> EventServiceOut:
    """Create a new Event-Service relationship"""
    # Verify that both event and service exist
    event = session.get(Event, event_id)
    if not event:
        raise HTTPException(status_code=404, detail="Event not found")
    
    service = session.get(Service, service_id)
    if not service:
        raise HTTPException(status_code=404, detail="Service not found")
    
    # --- FECHAS AWARE ---
    data_inicio = ensure_aware_utc(data_inicio)
    data_fin = ensure_aware_utc(data_fin)
    event.data_inici = ensure_aware_utc(event.data_inici)
    event.data_fi = ensure_aware_utc(event.data_fi)
    
    # Validate that service dates are within event dates
    if data_inicio < event.data_inici or data_fin > event.data_fi:
        raise HTTPException(
            status_code=400,
            detail="Service dates must be within event dates"
        )
    
    # Validate service availability
    if not _validate_service_availability(
        session=session,
        service_id=service_id,
        data_inicio=data_inicio,
        data_fin=data_fin
    ):
        raise HTTPException(
            status_code=400,
            detail="Service is not available for the specified time range"
        )
    
    # Validate service capacity
    if not _validate_service_capacity(
        session=session,
        service_id=service_id,
        data_inicio=data_inicio,
        data_fin=data_fin,
        cantidad=cantidad
    ):
        raise HTTPException(
            status_code=400,
            detail="Service does not have enough capacity for the specified time range"
        )
    
    # Check if relationship already exists
    existing = session.exec(
        select(EventService).where(
            EventService.event_id == event_id,
            EventService.service_id == service_id
        )
    ).first()
    
    if existing:
        raise HTTPException(status_code=400, detail="Service already added to this event")
    
    # Get the price for this time range
    price = _get_service_price(
        session=session,
        service_id=service_id,
        data_inicio=data_inicio,
        data_fin=data_fin
    )
    
    # Create the relationship
    event_service = EventService(
        event_id=event_id,
        service_id=service_id,
        cantidad=cantidad,
        data_inicio=data_inicio,
        data_fin=data_fin
    )
    session.add(event_service)
    session.commit()
    session.refresh(event_service)
    
    # Update event budget
    _update_event_budget(session, event_id)
    
    # Get the service details for the response
    service_dict = _prepare_service_dict(service, session)
    
    return EventServiceOut(
        id=event_service.id,
        event_id=event_service.event_id,
        cantidad=event_service.cantidad,
        data_inicio=event_service.data_inicio,
        data_fin=event_service.data_fin,
        price_total=event_service.cantidad * price,
        service=service_dict
    )

def update_event_service(
    *,
    session: Session,
    event_id: UUID,
    service_id: UUID,
    obj_in: EventServiceUpdate
) -> EventServiceOut:
    """Update an Event-Service relationship"""
    # Find the relationship
    event_service = session.exec(
        select(EventService).where(
            EventService.event_id == event_id,
            EventService.service_id == service_id
        )
    ).first()
    
    if not event_service:
        raise HTTPException(status_code=404, detail="Event-Service relationship not found")
    
    # Get the event to validate dates
    event = session.get(Event, event_id)
    if not event:
        raise HTTPException(status_code=404, detail="Event not found")
    
    # If cantidad is 0, delete the relationship
    if obj_in.cantidad == 0:
        session.delete(event_service)
        session.commit()
        _update_event_budget(session, event_id)
        return None
    
    # Get the new values, keeping existing ones if not provided
    new_cantidad = obj_in.cantidad if obj_in.cantidad is not None else event_service.cantidad
    new_data_inicio = obj_in.data_inicio if obj_in.data_inicio is not None else event_service.data_inicio
    new_data_fin = obj_in.data_fin if obj_in.data_fin is not None else event_service.data_fin
    
    # Validate that service dates are within event dates
    if new_data_inicio < event.data_inici or new_data_fin > event.data_fi:
        raise HTTPException(
            status_code=400,
            detail="Service dates must be within event dates"
        )
    
    # Validate service availability if dates are being updated
    if obj_in.data_inicio is not None or obj_in.data_fin is not None:
        if not _validate_service_availability(
            session=session,
            service_id=service_id,
            data_inicio=new_data_inicio,
            data_fin=new_data_fin
        ):
            raise HTTPException(
                status_code=400,
                detail="Service is not available for the specified time range"
            )
    
    # Validate service capacity if cantidad or dates are being updated
    if obj_in.cantidad is not None or obj_in.data_inicio is not None or obj_in.data_fin is not None:
        # Exclude current event_service from capacity check
        if not _validate_service_capacity(
            session=session,
            service_id=service_id,
            data_inicio=new_data_inicio,
            data_fin=new_data_fin,
            cantidad=new_cantidad,
            exclude_event_service_id=event_service.id
        ):
            raise HTTPException(
                status_code=400,
                detail="Service does not have enough capacity for the specified time range"
            )
    
    # Get the price for the new time range
    price = _get_service_price(
        session=session,
        service_id=service_id,
        data_inicio=new_data_inicio,
        data_fin=new_data_fin
    )
    
    # Update the relationship
    update_data = obj_in.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(event_service, field, value)
    
    session.add(event_service)
    session.commit()
    session.refresh(event_service)
    
    # Update event budget
    _update_event_budget(session, event_id)
    
    # Get the service details for the response
    service = session.get(Service, service_id)
    service_dict = _prepare_service_dict(service, session)
    
    return EventServiceOut(
        id=event_service.id,
        event_id=event_service.event_id,
        cantidad=event_service.cantidad,
        data_inicio=event_service.data_inicio,
        data_fin=event_service.data_fin,
        price_total=event_service.cantidad * price,
        service=service_dict
    )

def delete_event_service(
    *,
    session: Session,
    event_id: UUID,
    service_id: UUID
) -> None:
    """Delete an Event-Service relationship"""
    # Find the relationship
    event_service = session.exec(
        select(EventService).where(
            EventService.event_id == event_id,
            EventService.service_id == service_id
        )
    ).first()
    
    if not event_service:
        raise HTTPException(status_code=404, detail="Event-Service relationship not found")
    
    # Delete the relationship
    session.delete(event_service)
    session.commit()
    
    # Update event budget
    _update_event_budget(session, event_id)

def get_event_services(
    *,
    session: Session,
    event_id: UUID,
    skip: int = 0,
    limit: int = 100
) -> EventServicesOut:
    """Get all services for an event"""
    # Verify that event exists
    event = session.get(Event, event_id)
    if not event:
        raise HTTPException(status_code=404, detail="Event not found")
    
    # Get all services for the event
    statement = select(EventService, Service).join(
        Service, EventService.service_id == Service.id
    ).where(
        EventService.event_id == event_id
    ).offset(skip).limit(limit)
    
    results = session.exec(statement).all()
    
    # Construct the response
    event_services = []
    for event_service, service in results:
        service_dict = _prepare_service_dict(service, session)
        
        # Get the price from availability rules
        price = _get_service_price(
            session=session,
            service_id=service.id,
            data_inicio=event_service.data_inicio,
            data_fin=event_service.data_fin
        )
        
        # Get espai_propi details if it exists
        espai_propi_dict = None
        if event.espai_propi_id:
            espai_propi = session.get(EspaiPropi, event.espai_propi_id)
            if espai_propi:
                espai_propi_dict = {
                    "id": espai_propi.id,
                    "nombre": espai_propi.nombre,
                    "tipus": espai_propi.tipus,
                    "address": espai_propi.address,
                    "descripcion": espai_propi.descripcion,
                    "capacitat_aproximada": espai_propi.capacitat_aproximada,
                    "caracteristicas": espai_propi.caracteristicas,
                    "account_id": espai_propi.account_id
                }
        
        event_services.append(EventServiceOut(
            id=event_service.id,
            event_id=event_service.event_id,
            cantidad=event_service.cantidad,
            data_inicio=event_service.data_inicio,
            data_fin=event_service.data_fin,
            price_total=event_service.cantidad * price,
            service=service_dict
        ))
    
    # Get total count using func.count()
    count_statement = select(func.count()).select_from(
        select(EventService).where(EventService.event_id == event_id).subquery()
    )
    count = session.exec(count_statement).one()
    
    return EventServicesOut(data=event_services, count=count)

def get_all_services_for_event(
    *,
    session: Session,
    event_id: UUID
) -> EventAllServicesOut:
    """Get all services for an event with event details"""
    # Get the event with all its details
    event = session.get(Event, event_id)
    if not event:
        raise HTTPException(status_code=404, detail="Event not found")
    
    # Get all services for the event
    statement = select(Service).join(
        EventService, Service.id == EventService.service_id
    ).where(
        EventService.event_id == event_id
    )
    
    services = session.exec(statement).all()
    
    # Convert services to dict using _prepare_service_dict
    services_dict = [_prepare_service_dict(service, session) for service in services]
    
    # Get espai_propi details if it exists
    espai_propi_dict = None
    if event.espai_propi_id:
        espai_propi = session.get(EspaiPropi, event.espai_propi_id)
        if espai_propi:
            espai_propi_dict = {
                "id": espai_propi.id,
                "nombre": espai_propi.nombre,
                "tipus": espai_propi.tipus,
                "address": espai_propi.address,
                "descripcion": espai_propi.descripcion,
                "capacitat_aproximada": espai_propi.capacitat_aproximada,
                "caracteristicas": espai_propi.caracteristicas,
                "account_id": espai_propi.account_id
            }
    
    # Convert event to dict and add services
    event_dict = event.dict()
    event_dict["services"] = services_dict
    event_dict["espai_propi"] = espai_propi_dict
    
    return EventAllServicesOut(**event_dict) 


def get_events_for_service(
    *,
    session: Session,
    service_id: UUID
) -> List[EventAllServicesOut]:
    """Get all events where the service is included"""
    # Get all events for the service
    statement = select(Event).join(
        EventService, EventService.event_id == Event.id
    ).where(
        EventService.service_id == service_id
    ).order_by(Event.data_inici)
    
    events = session.exec(statement).all()
    
    # For each event, get all services details
    result = []
    for event in events:
        event_with_services = get_all_services_for_event(
            session=session,
            event_id=event.id
        )
        result.append(event_with_services)
    
    return result

def get_event_service(
    *,
    session: Session,
    event_id: UUID,
    service_id: UUID
) -> EventServiceOut:
    """Get a specific service for an event"""
    # Verify that event exists
    event = session.get(Event, event_id)
    if not event:
        raise HTTPException(status_code=404, detail="Event not found")
    
    # Get the specific service for the event
    statement = select(EventService, Service).join(
        Service, EventService.service_id == Service.id
    ).where(
        EventService.event_id == event_id,
        EventService.service_id == service_id
    )
    
    result = session.exec(statement).first()
    if not result:
        raise HTTPException(status_code=404, detail="Service not found in this event")
    
    event_service, service = result
    
    # Get the service details
    service_dict = _prepare_service_dict(service, session)
    
    # Get the price from availability rules
    price = _get_service_price(
        session=session,
        service_id=service.id,
        data_inicio=event_service.data_inicio,
        data_fin=event_service.data_fin
    )
    
    return EventServiceOut(
        id=event_service.id,
        event_id=event_service.event_id,
        cantidad=event_service.cantidad,
        data_inicio=event_service.data_inicio,
        data_fin=event_service.data_fin,
        price_total=event_service.cantidad * price,
        service=service_dict
    )


def get_service_availability_for_event( 
    *,
    session: Session,
    service: Service,
    eventData: EventData
) -> Tuple[int, datetime, datetime, float]:
    """Get the availability for a service in an event"""
    data_I = eventData.data_inici
    data_F = eventData.data_fi
    cantidad = 1
    tipo = service.type
    if tipo == ServiceType.MOBILIARI_DECORACIO:
        if not service.mobiliari_decoracio:
            raise HTTPException(status_code=400, detail="Service does not have mobiliari_decoracio")
        cantidad = calcular_cantidad_por_tipo(service.mobiliari_decoracio.tipologia, eventData.num_invitats)
    if tipo == "catering":
        cantidad = eventData.num_invitats
    # Verificar disponibilidad
    time_total = (data_F - data_I).total_seconds() / 3600  # Convertir a horas
    time_change = time_total // 4
    dispo, range =calcular_disponibilidad_reducida_2(session, service, tipo, eventData, time_change)
    if not dispo:
        raise HTTPException(status_code=400, detail="Service is not available for the specified time range")
    if range is None:
        raise HTTPException(status_code=400, detail="Service is not available for the specified time range")
    if range == 0:
        data_I = data_I + timedelta(hours=time_change)
        data_F = data_F - timedelta(hours=time_change)
    elif range == 1:
        data_I = data_I + timedelta(hours=time_change*2)
    elif range == 2:
        data_F = data_F - timedelta(hours=time_change*2)


    # Verificar capacidad
    if not _validate_service_capacity(
        session=session,
        service_id=service.id,
        data_inicio=data_I,
        data_fin=data_F,
        cantidad=cantidad
    ):
        raise HTTPException(status_code=400, detail="Service does not have enough capacity for the specified time range")

    # Obtener el precio para el rango de fechas
    try:
        price = _get_service_price(
            session=session,
            service_id=service.id,
            data_inicio=data_I,
            data_fin=data_F
        )
        price_final = price * cantidad
    except Exception as e:
        raise HTTPException(status_code=400, detail="Error getting service price")

    return cantidad, data_I, data_F, price_final
