""" CRUD operations for fotografia services with related entities """
from typing import List, Optional, Dict, Any, Tuple
from uuid import UUID

from sqlmodel import select, Session, func
from fastapi import HTTPException
from datetime import time

from app.models import (
    Fotografia, FotografiaType, ServicioEspecial,
    Service, ServiceType, ServiceFotografiaOut, ServiceCreateFotografia, ServiceUpdateFotografia,
    Raider, AvailabilityRule)


def create_fotografia(
    db: Session, 
    service_fotografia_in: ServiceCreateFotografia, 
    account_id: UUID
) -> ServiceFotografiaOut:
    """
    Create a new fotografia service with its related service data and raider
    """
    # Verificar que el tipo de servicio sea FOTOGRAFIA
    if service_fotografia_in.type != ServiceType.FOTOGRAFIA:
        service_fotografia_in.type = ServiceType.FOTOGRAFIA
    
    # Convertir el objeto Address a diccionario para que sea serializable
    address_dict = None
    if service_fotografia_in.address:
        address_dict = service_fotografia_in.address.dict() if hasattr(service_fotografia_in.address, 'dict') else service_fotografia_in.address.model_dump()
    
    # 1. Crear el Service con la dirección embebida como JSON
    service = Service(
        type=ServiceType.FOTOGRAFIA,
        name=service_fotografia_in.name,
        images=service_fotografia_in.images,
        description=service_fotografia_in.description,
        service_include=service_fotografia_in.service_include,
        account_id=account_id,
        address=address_dict
    )
    db.add(service)
    db.flush()  # Obtener el ID del servicio sin confirmar la transacción
    
    # 2. Crear las reglas de disponibilidad
    if service_fotografia_in.availability_rules:
        for rule_data in service_fotografia_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)
            )
            db.add(rule)
    
    # 3. Manejar el Raider
    raider_id = None
    raider = None
    if isinstance(service_fotografia_in.fotografia, dict) and service_fotografia_in.fotografia.get('raider'):
        raider_data = service_fotografia_in.fotografia['raider']
        
        # Buscar un raider existente con los mismos datos para esta cuenta
        existing_raider = db.query(Raider).filter(
            Raider.account_id == account_id,
            Raider.num_integrantes == raider_data.get('email'),
            Raider.equipo == raider_data.get('equipo'),
            Raider.solicitar_cancion_nueva == raider_data.get('solicitar_cancion_nueva'),
            Raider.equipo_propio == raider_data.get('equipo_propio'),
            Raider.dimensiones_escenario == raider_data.get('dimensiones_escenario'),
            Raider.condiciones == raider_data.get('condiciones'),
        ).first()
        
        if existing_raider:
            raider_id = existing_raider.id
            raider = existing_raider
        else:
            raider = Raider(**raider_data, account_id=account_id)
            db.add(raider)
            db.flush()  # Obtener el ID del raider
            raider_id = raider.id
    
    elif isinstance(service_fotografia_in.fotografia, dict) and service_fotografia_in.fotografia.get('raider_id'):
        raider_id = service_fotografia_in.fotografia['raider_id']
        raider = db.get(Raider, raider_id)
    # 4. Validar y crear la Fotografia
    fotografia_data = service_fotografia_in.fotografia.copy()
    if 'raider' in fotografia_data:
        del fotografia_data['raider']
    if 'raider_id' in fotografia_data:
        del fotografia_data['raider_id']
    
    # Validar fotografia_type
    if 'fotografia_type' in fotografia_data:
        try:
            FotografiaType(fotografia_data['fotografia_type'])
        except ValueError:
            raise HTTPException(
                status_code=400,
                detail=f"Tipo de fotografía inválido: {fotografia_data['fotografia_type']}"
            )
    
    # Validar servicio_especial_type si existe
    if fotografia_data.get('servicio_especial_type'):
        try:
            ServicioEspecial(fotografia_data['servicio_especial_type'])
        except ValueError:
            raise HTTPException(
                status_code=400,
                detail=f"Tipo de servicio especial inválido: {fotografia_data['servicio_especial_type']}"
            )
    
    fotografia = Fotografia(**fotografia_data, raider_id=raider_id)
    fotografia.id = service.id  # Usar el mismo ID que el servicio
    db.add(fotografia)
    
    # 5. Actualizar el servicio con el ID de fotografía
    service.fotografia_id = fotografia.id
    db.add(service)
    
    # Confirmar la transacción
    db.commit()
    db.refresh(service)
    db.refresh(fotografia)
    if raider:
        db.refresh(raider)
    
    # Construir y devolver el objeto de respuesta
    fotografia_dict = {
        "id": fotografia.id,
        "fotografia_type": fotografia.fotografia_type,
        "servicio_especial_type": fotografia.servicio_especial_type,
        "duracion": fotografia.duracion,
        "caracteristicas": fotografia.caracteristicas,
        "raider_id": fotografia.raider_id
    }
    
    # Incluir el raider si existe
    if raider:
        fotografia_dict["raider"] = raider.dict()
    
    # 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]
    
    return ServiceFotografiaOut(
        id=service.id,
        type=service.type,
        name=service.name,
        images=service.images,
        description=service.description,
        service_include=service.service_include,
        account_id=service.account_id,
        address=service.address,
        fotografia_id=fotografia.id,
        fotografia=fotografia_dict,
        availability_rules=availability_rules_dict
    )


def get_fotografia(db: Session, fotografia_id: UUID) -> Optional[ServiceFotografiaOut]:
    """
    Get a fotografia service by ID with all related data
    """
    # Obtener el servicio
    service = db.get(Service, fotografia_id)
    if not service or service.type != ServiceType.FOTOGRAFIA:
        return None
    
    # Obtener la fotografía relacionada
    fotografia = db.get(Fotografia, fotografia_id)
    if not fotografia:
        return None
    
    # Obtener el raider si existe
    raider = None
    if fotografia.raider_id:
        raider = db.get(Raider, fotografia.raider_id)
    
    # 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]
    
    # Construir el diccionario de fotografía
    fotografia_dict = {
        "id": fotografia.id,
        "fotografia_type": fotografia.fotografia_type,
        "servicio_especial_type": fotografia.servicio_especial_type,
        "duracion": fotografia.duracion,
        "caracteristicas": fotografia.caracteristicas,
        "raider_id": fotografia.raider_id
    }
    
    # Incluir el raider si existe
    if raider:
        fotografia_dict["raider"] = raider.dict()
    
    # Construir y devolver el objeto de respuesta
    return ServiceFotografiaOut(
        id=service.id,
        type=service.type,
        name=service.name,
        images=service.images,
        description=service.description,
        service_include=service.service_include,
        account_id=service.account_id,
        address=service.address,
        fotografia_id=fotografia.id,
        fotografia=fotografia_dict,
        availability_rules=availability_rules_dict
    )


def get_fotografias(
    db: Session, 
    account_id: Optional[UUID] = None,
    skip: int = 0, 
    limit: int = 100
) -> Tuple[List[ServiceFotografiaOut], int]:
    """
    Get all fotografia services with their related data
    """
    # Construir la consulta base
    query = select(Service, Fotografia).join(
        Fotografia, Service.id == Fotografia.id
    ).where(Service.type == ServiceType.FOTOGRAFIA)
    
    # Añadir filtro por account_id si se proporciona
    if account_id:
        query = query.where(Service.account_id == account_id)
    
    # Obtener el total de registros
    count_query = select(func.count()).select_from(query.subquery())
    total = db.exec(count_query).one()
    
    # Obtener los resultados paginados
    query = query.offset(skip).limit(limit)
    results = db.exec(query).all()
    
    # Construir la lista de resultados
    fotografias = []
    for service, fotografia in results:
        # Obtener el raider si existe
        raider = None
        if fotografia.raider_id:
            raider = db.get(Raider, fotografia.raider_id)
        
        # 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]
        
        # Construir el diccionario de fotografía
        fotografia_dict = {
            "id": fotografia.id,
            "fotografia_type": fotografia.fotografia_type,
            "servicio_especial_type": fotografia.servicio_especial_type,
            "duracion": fotografia.duracion,
            "caracteristicas": fotografia.caracteristicas,
            "raider_id": fotografia.raider_id
        }
        
        # Incluir el raider si existe
        if raider:
            fotografia_dict["raider"] = raider.dict()
        
        # Construir y añadir el objeto de respuesta
        fotografias.append(ServiceFotografiaOut(
            id=service.id,
            type=service.type,
            name=service.name,
            images=service.images,
            description=service.description,
            service_include=service.service_include,
            account_id=service.account_id,
            address=service.address,
            fotografia_id=fotografia.id,
            fotografia=fotografia_dict,
            availability_rules=availability_rules_dict
        ))
    
    return fotografias, total


def update_fotografia(
    db: Session, 
    fotografia_id: UUID, 
    service_fotografia_in: ServiceUpdateFotografia
) -> Optional[ServiceFotografiaOut]:
    """
    Update a fotografia service and its related data
    """
    # Obtener el servicio y la fotografía
    service = db.get(Service, fotografia_id)
    if not service or service.type != ServiceType.FOTOGRAFIA:
        return None
    
    fotografia = db.get(Fotografia, fotografia_id)
    if not fotografia:
        return None
    
    # Actualizar los campos del servicio
    service_data = service_fotografia_in.dict(exclude={"fotografia", "availability_rules"}, exclude_unset=True)
    for key, value in service_data.items():
        setattr(service, key, value)
    
    # Actualizar reglas de disponibilidad si se proporcionan
    if service_fotografia_in.availability_rules is not None:
        # Eliminar reglas existentes
        existing_rules = db.exec(
            select(AvailabilityRule).where(AvailabilityRule.service_id == service.id)
        ).all()
        for rule in existing_rules:
            db.delete(rule)
        
        # Crear nuevas reglas
        for rule_data in service_fotografia_in.availability_rules:
            rule = AvailabilityRule(
                service_id=service.id,
                dias_semana=rule_data.get('dias_semana', []),
                hora_inicio=rule_data.get('hora_inicio'),
                hora_fin=rule_data.get('hora_fin'),
                price=rule_data.get('price', 0.0),
                min_days_before=rule_data.get('min_days_before', 1)
            )
            db.add(rule)
    
    # Actualizar la fotografía si se proporciona
    if service_fotografia_in.fotografia:
        fotografia_data = service_fotografia_in.fotografia.copy()
        
        # Manejar el raider si existe
        if 'raider' in fotografia_data:
            raider_data = fotografia_data.pop('raider')
            
            # Si ya existe un raider, actualizarlo
            if fotografia.raider_id:
                raider = db.get(Raider, fotografia.raider_id)
                if raider:
                    for key, value in raider_data.items():
                        setattr(raider, key, value)
                    db.add(raider)
            else:
                # Crear un nuevo raider
                raider = Raider(**raider_data, account_id=service.account_id)
                db.add(raider)
                db.flush()
                fotografia.raider_id = raider.id
        
        # Actualizar los campos de la fotografía
        for key, value in fotografia_data.items():
            setattr(fotografia, key, value)
    
    # Guardar los cambios
    db.add(service)
    db.add(fotografia)
    db.commit()
    db.refresh(service)
    db.refresh(fotografia)
    
    # Obtener el raider actualizado si existe
    raider = None
    if fotografia.raider_id:
        raider = db.get(Raider, fotografia.raider_id)
    
    # 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]
    # Construir el diccionario de fotografía
    fotografia_dict = {
        "id": fotografia.id,
        "fotografia_type": fotografia.fotografia_type,
        "servicio_especial_type": fotografia.servicio_especial_type,
        "duracion": fotografia.duracion,
        "caracteristicas": fotografia.caracteristicas,
        "raider_id": fotografia.raider_id
    }
    
    # Incluir el raider si existe
    if raider:
        fotografia_dict["raider"] = raider.dict()
    # Construir y devolver el objeto de respuesta
    return ServiceFotografiaOut(
        id=service.id,
        type=service.type,
        name=service.name,
        images=service.images,
        description=service.description,
        service_include=service.service_include,
        account_id=service.account_id,
        address=service.address,
        fotografia_id=fotografia.id,
        fotografia=fotografia_dict,
        availability_rules=availability_rules_dict
    )


def delete_fotografia(db: Session, fotografia_id: UUID) -> bool:
    """
    Eliminar un servicio de fotografía y sus datos relacionados
    """
    try:
        # Obtener el servicio y la fotografía
        service = db.get(Service, fotografia_id)
        if not service or service.type != ServiceType.FOTOGRAFIA:
            raise HTTPException(status_code=404, detail="Servicio de fotografía no encontrado")
        
        fotografia = db.get(Fotografia, fotografia_id)
        if not fotografia:
            raise HTTPException(status_code=404, detail="Fotografía no encontrada")
        
        # Primero eliminamos las reglas de disponibilidad
        availability_rules = db.exec(
            select(AvailabilityRule).where(AvailabilityRule.service_id == service.id)
        ).all()
        for rule in availability_rules:
            db.delete(rule)
        db.flush()
        
        # Guardar el raider_id antes de eliminar la fotografía
        raider_id = fotografia.raider_id
        
        # Primero eliminamos la fotografía
        db.delete(fotografia)
        db.flush()
        
        # Luego eliminamos el servicio
        db.delete(service)
        db.flush()
        
        # Finalmente, si hay un raider, verificamos si podemos eliminarlo
        if raider_id:
            raider = db.get(Raider, raider_id)
            if raider:
                # Verificar si hay otras fotografías usando este raider
                fotografias_count = db.scalar(
                    select(func.count())
                    .select_from(Fotografia)
                    .where(Fotografia.raider_id == raider_id)
                )
                
                # Si no hay otras fotografías usando este raider, lo eliminamos
                if fotografias_count == 0:
                    db.delete(raider)
                    db.flush()
        
        # Confirmar la transacción
        db.commit()
        return True
        
    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=500,
            detail=f"Error al eliminar el servicio: {str(e)}"
        )


def get_fotografias_by_type(
    db: Session, 
    fotografia_type: FotografiaType, 
    account_id: Optional[UUID] = None,
    skip: int = 0, 
    limit: int = 100
) -> Tuple[List[ServiceFotografiaOut], int]:
    """
    Get fotografias by type, optionally filtered by account_id
    """
    # Construir la query base
    query = (
        select(Service, Fotografia)
        .join(Fotografia, Service.id == Fotografia.id)
        .where(
            Service.type == ServiceType.FOTOGRAFIA,
            Fotografia.fotografia_type == fotografia_type
        )
    )
    
    # Filtrar por account_id si se proporciona
    if account_id:
        query = query.where(Service.account_id == account_id)
    
    # Obtener el recuento total
    count_query = select(func.count()).select_from(query.subquery())
    total = db.scalar(count_query)
    
    # Aplicar pagination
    results = db.exec(query.offset(skip).limit(limit)).all()
    
    # Construir la respuesta
    fotografias = []
    for service, fotografia in results:
        # Obtener el raider si existe
        raider = None
        if fotografia.raider_id:
            raider = db.get(Raider, fotografia.raider_id)
        
        # Construir el diccionario de fotografía
        fotografia_dict = {
            "id": fotografia.id,
            "fotografia_type": fotografia.fotografia_type,
            "servicio_especial_type": fotografia.servicio_especial_type,
            "duracion": fotografia.duracion,
            "caracteristicas": fotografia.caracteristicas,
            "raider_id": fotografia.raider_id
        }
        
        # Incluir el raider si existe
        if raider:
            fotografia_dict["raider"] = raider.dict()
        
        # Añadir a la lista de resultados
        fotografias.append(
            ServiceFotografiaOut(
                id=service.id,
                type=service.type,
                name=service.name,
                images=service.images,
                description=service.description,
                service_include=service.service_include,
                account_id=service.account_id,
                address=service.address,
                fotografia_id=fotografia.id,
                fotografia=fotografia_dict
            )
        )
    
    return fotografias, total


def get_fotografias_by_special_service(
    db: Session, 
    servicio_especial_type: ServicioEspecial, 
    account_id: Optional[UUID] = None,
    skip: int = 0, 
    limit: int = 100
) -> Tuple[List[ServiceFotografiaOut], int]:
    """
    Get fotografias by special service type, optionally filtered by account_id
    """
    # Construir la query base
    query = (
        select(Service, Fotografia)
        .join(Fotografia, Service.id == Fotografia.id)
        .where(
            Service.type == ServiceType.FOTOGRAFIA,
            Fotografia.servicio_especial_type == servicio_especial_type
        )
    )
    
    # Filtrar por account_id si se proporciona
    if account_id:
        query = query.where(Service.account_id == account_id)
    
    # Obtener el recuento total
    count_query = select(func.count()).select_from(query.subquery())
    total = db.scalar(count_query)
    
    # Aplicar pagination
    results = db.exec(query.offset(skip).limit(limit)).all()
    
    # Construir la respuesta
    fotografias = []
    for service, fotografia in results:
        # Obtener el raider si existe
        raider = None
        if fotografia.raider_id:
            raider = db.get(Raider, fotografia.raider_id)
        
        # Construir el diccionario de fotografía
        fotografia_dict = {
            "id": fotografia.id,
            "fotografia_type": fotografia.fotografia_type,
            "servicio_especial_type": fotografia.servicio_especial_type,
            "duracion": fotografia.duracion,
            "caracteristicas": fotografia.caracteristicas,
            "raider_id": fotografia.raider_id
        }
        
        # Incluir el raider si existe
        if raider:
            fotografia_dict["raider"] = raider.dict()
        
        # Añadir a la lista de resultados
        fotografias.append(
            ServiceFotografiaOut(
                id=service.id,
                type=service.type,
                name=service.name,
                images=service.images,
                description=service.description,
                service_include=service.service_include,
                account_id=service.account_id,
                address=service.address,
                fotografia_id=fotografia.id,
                fotografia=fotografia_dict
            )
        )
    
    return fotografias, total


def get_fotografias_by_account(
    db: Session, 
    account_id: UUID,
    skip: int = 0, 
    limit: int = 100
) -> Tuple[List[ServiceFotografiaOut], int]:
    """
    Get fotografias for a specific account with pagination
    """
    # Construir la query base
    query = (
        select(Service, Fotografia)
        .join(Fotografia, Service.id == Fotografia.id)
        .where(
            Service.type == ServiceType.FOTOGRAFIA,
            Service.account_id == account_id
        )
    )
    
    # Obtener el recuento total
    count_query = select(func.count()).select_from(query.subquery())
    total = db.scalar(count_query)
    
    # Aplicar pagination
    results = db.exec(query.offset(skip).limit(limit)).all()
    
    # Construir la respuesta
    fotografias = []
    for service, fotografia in results:
        # Obtener el raider si existe
        raider = None
        if fotografia.raider_id:
            raider = db.get(Raider, fotografia.raider_id)
        
        # Construir el diccionario de fotografía
        fotografia_dict = {
            "id": fotografia.id,
            "fotografia_type": fotografia.fotografia_type,
            "servicio_especial_type": fotografia.servicio_especial_type,
            "duracion": fotografia.duracion,
            "caracteristicas": fotografia.caracteristicas,
            "raider_id": fotografia.raider_id
        }
        
        # Incluir el raider si existe
        if raider:
            fotografia_dict["raider"] = raider.dict()
        
        # Añadir a la lista de resultados
        fotografias.append(
            ServiceFotografiaOut(
                id=service.id,
                type=service.type,
                name=service.name,
                images=service.images,
                description=service.description,
                service_include=service.service_include,
                account_id=service.account_id,
                address=service.address,
                fotografia_id=fotografia.id,
                fotografia=fotografia_dict
            )
        )
    
    return fotografias, total


def get_fotografias_by_servicio_especial(
    db: Session, 
    servicio_especial: ServicioEspecial, 
    account_id: Optional[UUID] = None,
    skip: int = 0, 
    limit: int = 100
) -> Tuple[List[ServiceFotografiaOut], int]:
    """
    Get fotografias by special service type, optionally filtered by account_id
    """
    # Llamar a la función existente con el nombre correcto
    return get_fotografias_by_special_service(
        db=db,
        servicio_especial_type=servicio_especial,
        account_id=account_id,
        skip=skip,
        limit=limit
    )


def get_fotografias_filtered(
    db: Session,
    fotografia_type: Optional[FotografiaType] = None,
    servicio_especial: Optional[ServicioEspecial] = None,
    min_duracion: Optional[float] = None,
    max_duracion: Optional[float] = None,
    account_id: Optional[UUID] = None,
    skip: int = 0,
    limit: int = 100
) -> Tuple[List[ServiceFotografiaOut], int]:
    """
    Get fotografias with multiple filters:
    - fotografia_type: Filter by type of photography service
    - servicio_especial: Filter by special service type
    - min_duracion: Filter by minimum duration (in hours, can be decimal)
    - max_duracion: Filter by maximum duration (in hours, can be decimal)
    - account_id: Filter by account (optional)
    """
    # Construir la query base
    query = (
        select(Service, Fotografia)
        .join(Fotografia, Service.id == Fotografia.id)
        .where(Service.type == ServiceType.FOTOGRAFIA)
    )
    
    # Aplicar filtros si se proporcionan
    if fotografia_type:
        query = query.where(Fotografia.fotografia_type == fotografia_type)
    
    if servicio_especial:
        query = query.where(Fotografia.servicio_especial_type == servicio_especial)
    
    if min_duracion is not None:
        query = query.where(Fotografia.duracion >= min_duracion)
    
    if max_duracion is not None:
        query = query.where(Fotografia.duracion <= max_duracion)
    
    # Filtrar por account_id si se proporciona
    if account_id:
        query = query.where(Service.account_id == account_id)
    
    # Obtener el recuento total
    count_query = select(func.count()).select_from(query.subquery())
    total = db.scalar(count_query)
    
    # Aplicar pagination
    results = db.exec(query.offset(skip).limit(limit)).all()
    
    # Construir la respuesta
    fotografias = []
    for service, fotografia in results:
        # Obtener el raider si existe
        raider = None
        if fotografia.raider_id:
            raider = db.get(Raider, fotografia.raider_id)
        
        # 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]
        
        # Construir el diccionario de fotografía
        fotografia_dict = {
            "id": fotografia.id,
            "fotografia_type": fotografia.fotografia_type,
            "servicio_especial_type": fotografia.servicio_especial_type,
            "duracion": fotografia.duracion,
            "caracteristicas": fotografia.caracteristicas,
            "raider_id": fotografia.raider_id
        }
        
        # Incluir el raider si existe
        if raider:
            fotografia_dict["raider"] = raider.dict()
        
        # Añadir a la lista de resultados
        fotografias.append(
            ServiceFotografiaOut(
                id=service.id,
                type=service.type,
                name=service.name,
                images=service.images,
                description=service.description,
                service_include=service.service_include,
                account_id=service.account_id,
                address=service.address,
                fotografia_id=fotografia.id,
                fotografia=fotografia_dict,
                availability_rules=availability_rules_dict
            )
        )
    
    return fotografias, total 