""" CRUD operations for espai services """
from typing import List, Optional, Dict, Tuple, Any
from uuid import UUID
from datetime import time

from sqlmodel import select, Session, func
from fastapi import HTTPException
from sqlalchemy import or_, JSON

from app.models import (
    Espai, EspaiCreate, EspaiUpdate, EspaiType,
    Service, ServiceCreateEspai, ServiceUpdateEspai, ServiceEspaiOut, ServiceType, ServiceEspaisOut,
    Address, AvailabilityRule, AvailabilityRuleCreate
)



def create_espai_service(*, session: Session, obj_in: ServiceCreateEspai, account_id: UUID) -> ServiceEspaiOut:
    """Create a new espai service for an account"""
    # Convertir el objeto Address a diccionario
    address_dict = None
    if obj_in.address:
        address_dict = obj_in.address.dict() if hasattr(obj_in.address, 'dict') else obj_in.address.model_dump()
    
    # 1. Crear el Service con la dirección embebida como JSON
    service = Service(
        type=ServiceType.ESPAI,
        name=obj_in.name,
        images=obj_in.images,
        description=obj_in.description,
        service_include=obj_in.service_include,
        account_id=account_id,
        address=address_dict
    )
    session.add(service)
    session.flush()  # To get the service ID
    
    # 2. Crear las reglas de disponibilidad
    if obj_in.availability_rules:
        for rule_data in obj_in.availability_rules:
            # Convertir strings de tiempo a objetos time
            hora_inicio = time.fromisoformat(rule_data.get('hora_inicio')) if rule_data.get('hora_inicio') else None
            hora_fin = time.fromisoformat(rule_data.get('hora_fin')) if rule_data.get('hora_fin') else None
            
            rule = AvailabilityRule(
                service_id=service.id,
                dias_semana=rule_data.get('dias_semana', []),
                hora_inicio=hora_inicio,
                hora_fin=hora_fin,
                price=rule_data.get('price', 0.0),
                min_days_before=rule_data.get('min_days_before', 1)
            )
            session.add(rule)
    
    espai_data  = obj_in.espai.copy()
    if 'espai_type' in espai_data:
        try:
            EspaiType(espai_data['espai_type'])
        except ValueError:
            raise HTTPException(
                status_code=400,
                detail=f"Tipo de espai inválido: {espai_data['espai_type']}"
            )
    espai = Espai(
        **espai_data,
        id=service.id  # Use service.id as espai.id
    )
    session.add(espai)
    service.espai_id = espai.id
    session.add(service)
    session.commit()
    session.refresh(service)
    session.refresh(espai)
    espai_dict = espai.dict()
    
    # Obtener las reglas de disponibilidad
    availability_rules = session.exec(
        select(AvailabilityRule)
        .where(AvailabilityRule.service_id == service.id)
    ).all()
    availability_rules_dict = [rule.dict() for rule in availability_rules]
    
    result = ServiceEspaiOut(
        id=service.id, 
        type=service.type,
        name=service.name,
        images=service.images,
        description=service.description,
        service_include=service.service_include,
        address=service.address,
        account_id=service.account_id,
        espai_id=espai.id,
        espai=espai_dict,
        availability_rules=availability_rules_dict
    )
    return result


def get_espai_service(*, session: Session, service_id: UUID) -> Optional[ServiceEspaiOut]:
    """Get an espai service by ID"""
    # Join query to get service with related espai
    result = session.exec(
        select(Service, Espai)
        .join(Espai, Service.id == Espai.id)
        .where(Service.id == service_id)
    ).first()
    
    if not result:
        return None
    
    service, espai = result
    
    # Get availability rules
    availability_rules = session.exec(
        select(AvailabilityRule)
        .where(AvailabilityRule.service_id == service.id)
    ).all()
    
    # Construct the response
    espai_dict = espai.dict()
    availability_rules_dict = [rule.dict() for rule in availability_rules]
    
    return ServiceEspaiOut(
        id=service.id, 
        type=service.type,
        name=service.name,
        images=service.images,
        description=service.description,
        service_include=service.service_include,
        address=service.address,
        account_id=service.account_id,
        espai_id=espai.id,
        espai=espai_dict,
        availability_rules=availability_rules_dict
    )




def update_espai_service(*, session: Session, service: Service, espai: Espai, obj_in: ServiceUpdateEspai) -> ServiceEspaiOut:
    """Update an espai service"""
    service_data = obj_in.dict(exclude={"espai", "availability_rules"}, exclude_unset=True)
    address_dict = None
    if service_data.get("address"):
        address = service_data["address"]
        address_dict = address.dict() if hasattr(address, 'dict') else address
        service_data["address"] = address_dict
    for key, value in service_data.items():
        setattr(service, key, value)
    
    # Actualizar reglas de disponibilidad si se proporcionan
    if obj_in.availability_rules is not None:
        # Eliminar reglas existentes
        existing_rules = session.exec(
            select(AvailabilityRule).where(AvailabilityRule.service_id == service.id)
        ).all()
        for rule in existing_rules:
            session.delete(rule)
        
        # Crear nuevas reglas
        for rule_data in obj_in.availability_rules:
            hora_inicio = time.fromisoformat(rule_data.get('hora_inicio')) if rule_data.get('hora_inicio') else None
            hora_fin = time.fromisoformat(rule_data.get('hora_fin')) if rule_data.get('hora_fin') else None
            rule = AvailabilityRule(
                service_id=service.id,
                dias_semana=rule_data.get('dias_semana', []),
                hora_inicio=hora_inicio,
                hora_fin=hora_fin,
                price=rule_data.get('price', 0.0),
                min_days_before=rule_data.get('min_days_before', 1)
            )
            session.add(rule)
    
    if obj_in.espai and isinstance(obj_in.espai, dict):
        espai_data = obj_in.espai.copy()
        for key, value in espai_data.items():
            setattr(espai, key, value)
    session.add(service)
    session.add(espai)
    session.commit()
    session.refresh(service)
    session.refresh(espai)
    espai_dict = espai.dict()
    availability_rules = session.exec(
        select(AvailabilityRule)
        .where(AvailabilityRule.service_id == service.id)
    ).all()
    availability_rules_dict = [rule.dict() for rule in availability_rules]
    return ServiceEspaiOut(
        id=service.id, 
        type=service.type,
        name=service.name,
        images=service.images,
        description=service.description,
        service_include=service.service_include,
        address=service.address,
        account_id=service.account_id,
        espai_id=espai.id,
        espai=espai_dict,
        availability_rules=availability_rules_dict
    )


def delete_espai_service(*, session: Session, service: Service, espai: Espai) -> None:
    """Delete an espai service"""
    # Eliminar reglas de disponibilidad asociadas
    rules = session.exec(
        select(AvailabilityRule).where(AvailabilityRule.service_id == service.id)
    ).all()
    if rules:
        for rule in rules:
            session.delete(rule)
    # Borrar el espai y el service
    session.delete(espai)
    session.delete(service)
    session.commit()



def get_espais_filtered(
    db: Session,
    espai_type: Optional[EspaiType] = None,
    num_capacitat: Optional[int] = None,
    caracteristicas: Optional[List[str]] = None,
    account_id: Optional[UUID] = None,
    skip: int = 0,
    limit: int = 100
) -> ServiceEspaisOut:
    """
    Get filtered espai services with pagination and optional filters
    """
    query = db.query(Service, Espai).join(Espai, Service.id == Espai.id).where(Service.type == ServiceType.ESPAI)
    if espai_type:
        query = query.filter(Espai.espai_type == espai_type)
    if num_capacitat is not None:
        query = query.filter(Espai.capacitat_min <= num_capacitat)
        query = query.filter(Espai.capacitat_max >= num_capacitat)
    if caracteristicas:
        caracteristicas_filters = [Espai.caracteristicas.contains(c) for c in caracteristicas]
        query = query.filter(or_(*caracteristicas_filters))
    if account_id:
        query = query.filter(Service.account_id == account_id)
    total = query.count()
    results = query.offset(skip).limit(limit).all()
    espais = []
    for service, espai in results:
        # Obtener las reglas de disponibilidad
        availability_rules = db.exec(
            select(AvailabilityRule)
            .where(AvailabilityRule.service_id == service.id)
        ).all()
        availability_rules_dict = [rule.dict() for rule in availability_rules]
        
        espai_dict = espai.dict()
        espais.append(ServiceEspaiOut(
            id=service.id, 
            type=service.type,
            name=service.name,
            images=service.images,
            description=service.description,
            service_include=service.service_include,
            address=service.address,
            account_id=service.account_id,
            espai_id=espai.id,
            espai=espai_dict,
            availability_rules=availability_rules_dict
        ))
    return ServiceEspaisOut(data=espais, count=total)


def get_all_espais(db: Session, skip: int = 0, limit: int = 100) -> ServiceEspaisOut:
    query = db.query(Service, Espai).join(Espai, Service.id == Espai.id).where(Service.type == ServiceType.ESPAI)
    total = query.count()
    results = query.offset(skip).limit(limit).all()
    espais = []
    for service, espai in results:
        # Obtener las reglas de disponibilidad
        availability_rules = db.exec(
            select(AvailabilityRule)
            .where(AvailabilityRule.service_id == service.id)
        ).all()
        availability_rules_dict = [rule.dict() for rule in availability_rules]
        
        espai_dict = espai.dict()
        espais.append(ServiceEspaiOut(
            id=service.id, 
            type=service.type,
            name=service.name,
            images=service.images,
            description=service.description,
            service_include=service.service_include,
            address=service.address,
            account_id=service.account_id,
            espai_id=espai.id,
            espai=espai_dict,
            availability_rules=availability_rules_dict
        ))
    return ServiceEspaisOut(data=espais, count=total)


def get_all_espais_me(db: Session, account_id: UUID, skip: int = 0, limit: int = 100) -> ServiceEspaisOut:
    query = db.query(Service, Espai).join(Espai, Service.id == Espai.id).where(Service.type == ServiceType.ESPAI, Service.account_id == account_id)
    total = query.count()
    results = query.offset(skip).limit(limit).all()
    espais = []
    for service, espai in results:
        # Obtener las reglas de disponibilidad
        availability_rules = db.exec(
            select(AvailabilityRule)
            .where(AvailabilityRule.service_id == service.id)
        ).all()
        availability_rules_dict = [rule.dict() for rule in availability_rules]
        
        espai_dict = espai.dict()
        espais.append(ServiceEspaiOut(
            id=service.id, 
            type=service.type,
            name=service.name,
            images=service.images,
            description=service.description,
            service_include=service.service_include,
            address=service.address,
            account_id=service.account_id,
            espai_id=espai.id,  
            espai=espai_dict,
            availability_rules=availability_rules_dict
        ))
    return ServiceEspaisOut(data=espais, count=total) 