""" Event related CRUD methods """
import uuid
from typing import Any, List, Optional, Tuple
from datetime import datetime, timezone, timedelta

from sqlmodel import Session, select, col, func

from app.models import Event, EventCreate, EventUpdate, TipusEvent, EspaiPropi, TipusEspaiPropi, EventOut, EventsOut, AvailabilityRule, ServiceType, EstadoEvent
from fastapi import HTTPException

def _check_unique_event_name(
    *,
    session: Session,
    name: str,
    account_id: uuid.UUID,
    exclude_event_id: Optional[uuid.UUID] = None
) -> None:
    """Check if event name is unique for the account"""
    query = select(Event).where(
        Event.name == name,
        Event.account_id == account_id
    )
    
    if exclude_event_id:
        query = query.where(Event.id != exclude_event_id)
    
    existing_event = session.exec(query).first()
    if existing_event:
        raise HTTPException(
            status_code=400,
            detail=f"An event with name '{name}' already exists for this account"
        )

def create_event(
    *,
    session: Session,
    obj_in: EventCreate,
    account_id: uuid.UUID
) -> Event:
    """Create a new event"""
    # Check if event name is unique for this account
    _check_unique_event_name(
        session=session,
        name=obj_in.name,
        account_id=account_id
    )
    
    espai_propi_id = None
    
    # Create espai_propi if provided
    if obj_in.espai_propi:
        try:
            # Validar que el tipo de espacio propio es válido
            tipus = obj_in.espai_propi.get("tipus")
            if tipus not in [t.value for t in TipusEspaiPropi]:
                raise HTTPException(
                    status_code=400,
                    detail=f"Invalid espai_propi type. Must be one of: {[t.value for t in TipusEspaiPropi]}"
                )
            
            espai_propi = EspaiPropi(
                nombre=obj_in.espai_propi.get("nombre", ""),
                tipus=tipus,
                address=obj_in.espai_propi.get("address"),
                descripcion=obj_in.espai_propi.get("descripcion"),
                capacitat_aproximada=obj_in.espai_propi.get("capacitat_aproximada"),
                caracteristicas=obj_in.espai_propi.get("caracteristicas", []),
                account_id=account_id
            )
            session.add(espai_propi)
            session.commit()
            session.refresh(espai_propi)
            espai_propi_id = espai_propi.id
        except ValueError as e:
            raise HTTPException(
                status_code=400,
                detail=f"Invalid espai_propi data: {str(e)}"
            )
    elif obj_in.espai_propi_id:
        # Verify that espai_propi exists and belongs to the account
        espai_propi = session.get(EspaiPropi, obj_in.espai_propi_id)
        if not espai_propi or espai_propi.account_id != account_id:
            raise HTTPException(
                status_code=404,
                detail="Espai propi not found or does not belong to the account"
            )
        espai_propi_id = obj_in.espai_propi_id
    
    # Create the event
    event = Event(
        name=obj_in.name,
        tipus=obj_in.tipus,
        pressupost=obj_in.pressupost,
        data_inici=obj_in.data_inici,
        data_fi=obj_in.data_fi,
        num_invitats=obj_in.num_invitats,
        espai_propi_id=espai_propi_id,
        account_id=account_id,
        estat=obj_in.estat if obj_in.estat is not None else EstadoEvent.GUARDADO
    )
    session.add(event)
    session.commit()
    session.refresh(event)
    
    # 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
            }
    
    # Construct the response
    return EventOut(
        id=event.id,
        name=event.name,
        tipus=event.tipus,
        pressupost=event.pressupost,
        data_inici=event.data_inici,
        data_fi=event.data_fi,
        num_invitats=event.num_invitats,
        espai_propi_id=event.espai_propi_id,
        espai_propi=espai_propi_dict,
        account_id=event.account_id,
        estat=event.estat
    )

def _build_event_dict(event: Event, session: Session) -> dict:
    """Build event dictionary with all related data"""
    # 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
            }
    
    # Get services details if they exist
    services_dict = []
    if hasattr(event, 'services'):
        for service in event.services:
            service_dict = {
                "id": service.id,
                "name": service.name,
                "type": service.type,
                "images": service.images,
                "description": service.description,
                "service_include": service.service_include,
                "account_id": service.account_id
            }
            
            # Get availability rules
            rules = session.exec(
                select(AvailabilityRule).where(AvailabilityRule.service_id == service.id)
            ).all()
            service_dict["availability_rules"] = [rule.dict() for rule in rules]
            
            # Get espai details if it's an espai service
            if service.type == ServiceType.ESPAI and hasattr(service, 'espai'):
                service_dict["espai"] = {
                    "id": service.espai.id,
                    "espai_type": service.espai.espai_type,
                    "capacitat_min": service.espai.capacitat_min,
                    "capacitat_max": service.espai.capacitat_max,
                    "caracteristicas": service.espai.caracteristicas
                }
            
            services_dict.append(service_dict)
    
    return {
        "id": event.id,
        "name": event.name,
        "tipus": event.tipus,
        "pressupost": event.pressupost,
        "data_inici": event.data_inici,
        "data_fi": event.data_fi,
        "num_invitats": event.num_invitats,
        "espai_propi_id": event.espai_propi_id,
        "espai_propi": espai_propi_dict,
        "account_id": event.account_id,
        "services": services_dict,
        "estat": event.estat
    }

def get_event_by_id(*, session: Session, event_id: uuid.UUID) -> Event | None:
    """
    Get event by id
    """
    return session.get(Event, event_id)

def get_event_by_id_out(*, session: Session, event_id: uuid.UUID) -> EventOut | None:
    """
    Get event by id and return as EventOut
    """
    event = session.get(Event, event_id)
    if not event:
        return None
    
    event_dict = _build_event_dict(event, session)
    return EventOut(**event_dict)

def get_events_by_account(
    *,
    session: Session,
    account_id: uuid.UUID,
    skip: int = 0,
    limit: int = 100
) -> tuple[List[EventOut], int]:
    """
    Get events by account id with pagination
    """
    # Count query
    count_statement = select(func.count(Event.id)).where(
        Event.account_id == account_id
    )
    count = session.exec(count_statement).one()
    
    # Results query
    statement = select(Event).where(
        Event.account_id == account_id
    ).offset(skip).limit(limit)
    events = session.exec(statement).all()
    
    # Convert events to dict
    events_dict = [_build_event_dict(event, session) for event in events]
    
    return [EventOut(**event_dict) for event_dict in events_dict], count

def get_all_events(
    *,
    session: Session,
    skip: int = 0,
    limit: int = 100
) -> tuple[List[EventOut], int]:
    """
    Get all events with pagination
    """
    count_statement = select(func.count()).select_from(Event)
    count = session.exec(count_statement).one()
    
    statement = select(Event).offset(skip).limit(limit)
    events = session.exec(statement).all()
    
    # Convert events to dict
    events_dict = [_build_event_dict(event, session) for event in events]
    
    return [EventOut(**event_dict) for event_dict in events_dict], count

def update_event(*, session: Session, event: Event, event_in: EventUpdate) -> EventOut:
    """
    Update event
    """
    # Check if event name is unique for this account (if name is being updated)
    if event_in.name is not None and event_in.name != event.name:
        _check_unique_event_name(
            session=session,
            name=event_in.name,
            account_id=event.account_id,
            exclude_event_id=event.id
        )
    
    # Si se proporciona un nuevo espai_propi, crearlo
    if event_in.espai_propi:
        try:
            # Validar que el tipo de espacio propio es válido
            tipus = event_in.espai_propi.get("tipus")
            if tipus not in [t.value for t in TipusEspaiPropi]:
                raise HTTPException(
                    status_code=400,
                    detail=f"Invalid espai_propi type. Must be one of: {[t.value for t in TipusEspaiPropi]}"
                )
            
            # Si ya existe un espai_propi, eliminarlo
            if event.espai_propi_id:
                old_espai_propi = session.get(EspaiPropi, event.espai_propi_id)
                if old_espai_propi:
                    session.delete(old_espai_propi)
            
            # Crear nuevo espai_propi
            espai_propi = EspaiPropi(
                nombre=event_in.espai_propi.get("nombre", ""),
                tipus=tipus,
                address=event_in.espai_propi.get("address"),
                descripcion=event_in.espai_propi.get("descripcion"),
                capacitat_aproximada=event_in.espai_propi.get("capacitat_aproximada"),
                caracteristicas=event_in.espai_propi.get("caracteristicas", []),
                account_id=event.account_id
            )
            session.add(espai_propi)
            session.commit()
            session.refresh(espai_propi)
            event.espai_propi_id = espai_propi.id
        except ValueError as e:
            raise HTTPException(
                status_code=400,
                detail=f"Invalid espai_propi data: {str(e)}"
            )
    
    # Actualizar el resto de campos
    event_data = event_in.model_dump(exclude_unset=True, exclude={"espai_propi"})
    for key, value in event_data.items():
        setattr(event, key, value)
    
    session.add(event)
    session.commit()
    session.refresh(event)
    
    event_dict = _build_event_dict(event, session)
    return EventOut(**event_dict)

def delete_event(*, session: Session, event: Event) -> None:
    """
    Delete event
    """
    session.delete(event)
    session.commit()

def get_events_by_type(
    *,
    session: Session,
    account_id: uuid.UUID,
    tipus: TipusEvent,
    skip: int = 0,
    limit: int = 100
) -> tuple[List[EventOut], int]:
    """
    Get events by type with pagination
    """
    count_statement = select(func.count(Event.id)).where(
        Event.account_id == account_id,
        Event.tipus == tipus
    )
    count = session.exec(count_statement).one()
    
    statement = select(Event).where(
        Event.account_id == account_id,
        Event.tipus == tipus
    ).offset(skip).limit(limit)
    events = session.exec(statement).all()
    
    # Convert events to dict
    events_dict = [_build_event_dict(event, session) for event in events]
    
    return [EventOut(**event_dict) for event_dict in events_dict], count

def get_upcoming_events(
    *,
    session: Session,
    account_id: uuid.UUID,
    days: int = 30,
    skip: int = 0,
    limit: int = 100
) -> Tuple[List[EventOut], int]:
    """Get upcoming events for an account"""
    # Get current date in UTC
    now = datetime.now(timezone.utc)
    # Calculate future date using timedelta
    future_date = now + timedelta(days=days)
    
    # Count query
    count_statement = select(func.count(Event.id)).where(
        Event.account_id == account_id,
        Event.data_inici >= now,
        Event.data_inici <= future_date
    )
    count = session.exec(count_statement).one()
    
    # Results query
    statement = select(Event).where(
        Event.account_id == account_id,
        Event.data_inici >= now,
        Event.data_inici <= future_date
    ).order_by(Event.data_inici).offset(skip).limit(limit)
    
    events = session.exec(statement).all()
    
    # Convert events to dict
    events_dict = [_build_event_dict(event, session) for event in events]
    
    return [EventOut(**event_dict) for event_dict in events_dict], count

def filter_events(
    *,
    session: Session,
    account_id: uuid.UUID,
    has_espai_propi: Optional[bool] = None,
    min_invitats: Optional[int] = None,
    max_invitats: Optional[int] = None,
    min_pressupost: Optional[float] = None,
    max_pressupost: Optional[float] = None,
    days_until: Optional[int] = None,
    tipus: Optional[TipusEvent] = None,
    skip: int = 0,
    limit: int = 100,
    only_mine: bool = False
) -> tuple[List[EventOut], int]:
    """
    Get events with filters
    """
    # Base query
    query = select(Event)
    
    # Apply filters
    if only_mine:
        query = query.where(Event.account_id == account_id)
    
    if has_espai_propi is not None:
        if has_espai_propi:
            query = query.where(Event.espai_propi_id.is_not(None))
        else:
            query = query.where(Event.espai_propi_id.is_(None))
    
    if min_invitats is not None:
        query = query.where(Event.num_invitats >= min_invitats)
    
    if max_invitats is not None:
        query = query.where(Event.num_invitats <= max_invitats)
    
    if min_pressupost is not None:
        query = query.where(Event.pressupost >= min_pressupost)
    
    if max_pressupost is not None:
        query = query.where(Event.pressupost <= max_pressupost)
    
    if tipus is not None:
        query = query.where(Event.tipus == tipus)
    
    if days_until is not None:
        today = datetime.now(timezone.utc)
        future_date = today + timedelta(days=days_until)
        query = query.where(Event.data_inici <= future_date)
    
    # Get total count
    count_query = select(func.count()).select_from(query.subquery())
    total_count = session.exec(count_query).one()
    
    # Apply pagination
    query = query.offset(skip).limit(limit)
    
    # Execute query
    events = session.exec(query).all()
    
    # Convert to EventOut
    event_outs = []
    for event in events:
        event_dict = _build_event_dict(event, session)
        event_outs.append(EventOut(**event_dict))
    
    return event_outs, total_count 