import asyncio
import os
from bullmq import Queue
import logging
from typing import Dict, Optional, List

# Configurar logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def bullmq_cancel_execution(job_id: str, queue_name: str = None, redis_url: str = None) -> Dict:
    """
    Cancela la ejecución de un trabajo específico de BullMQ.
    Elimina trabajos que están en delay, waiting, o failed para evitar que se procesen.
    
    Args:
        job_id: ID del trabajo a cancelar
        queue_name: Nombre de la cola donde buscar el trabajo (opcional si no se conoce)
        redis_url: URL de Redis (opcional, usa variable de entorno por defecto)
    
    Returns:
        Dict: Resultado de la cancelación con información del estado
    """
    if redis_url is None:
        redis_url = os.environ.get('REDIS_HOST', 'redis://192.168.1.163:6379')
    
    async def cancel_job():
        try:
            # Si no se especifica queue_name, intentar buscar en colas comunes
            search_queues = []
            if queue_name:
                search_queues = [queue_name]
            else:
                # Colas comunes donde buscar
                search_queues = [
                    "test_queue", "priority_test_queue", "activation_test_queue",
                    "delay_test_queue", "calibration", "processing", "email",
                    "integration_test_queue", "test-delay", "test_custom_id"
                ]
            
            job_found = False
            cancellation_result = {
                "job_id": job_id,
                "cancelled": False,
                "status": "not_found",
                "queue_searched": [],
                "error": None
            }
            
            for queue_name_search in search_queues:
                try:
                    queue = Queue(queue_name_search, {"connection": redis_url})
                    cancellation_result["queue_searched"].append(queue_name_search)
                    
                    # Buscar el trabajo en diferentes estados
                    job = await queue.getJob(job_id)
                    
                    # Si no se encuentra directamente, buscar en listas de jobs
                    if not job:
                        # Buscar en diferentes estados
                        job_states = ["waiting", "delayed", "prioritized", "failed", "completed", "active"]
                        for state in job_states:
                            try:
                                jobs = await queue.getJobs([state], 0, 100)  # Buscar más jobs
                                for candidate_job in jobs:
                                    # Comparar tanto job.id como job.name
                                    if (candidate_job.id == job_id or 
                                        candidate_job.name == job_id or 
                                        (hasattr(candidate_job, 'data') and 
                                         candidate_job.data.get('_custom_job_id') == job_id)):
                                        job = candidate_job
                                        logger.info(f"Found job {job_id} with internal ID {job.id} in state {state}")
                                        break
                                if job:
                                    break
                            except Exception as e:
                                logger.debug(f"Error searching {state} jobs: {e}")
                                continue
                    
                    if job:
                        job_found = True
                        cancellation_result["found_in_queue"] = queue_name_search
                        
                        # Verificar el estado actual del trabajo
                        job_state = await job.getState()
                        cancellation_result["original_state"] = job_state
                        cancellation_result["internal_job_id"] = job.id
                        
                        # Solo cancelar trabajos que no han sido completados
                        if job_state in ["waiting", "delayed", "prioritized", "failed"]:
                            # Intentar remover el trabajo
                            await job.remove()
                            
                            cancellation_result.update({
                                "cancelled": True,
                                "status": "cancelled",
                                "message": f"Job {job_id} cancelled successfully from state '{job_state}'"
                            })
                            
                            logger.info(f"Job {job_id} (internal ID: {job.id}) cancelled successfully from queue '{queue_name_search}' (state: {job_state})")
                            
                        elif job_state in ["active", "completed"]:
                            cancellation_result.update({
                                "cancelled": False,
                                "status": "too_late",
                                "message": f"Job {job_id} cannot be cancelled - already {job_state}"
                            })
                            
                            logger.warning(f"Cannot cancel job {job_id} - already {job_state}")
                            
                        else:
                            cancellation_result.update({
                                "cancelled": False,
                                "status": "unknown_state",
                                "message": f"Job {job_id} in unknown state: {job_state}"
                            })
                        
                        await queue.close()
                        break
                    
                    await queue.close()
                    
                except Exception as e:
                    logger.debug(f"Error searching in queue '{queue_name_search}': {str(e)}")
                    continue
            
            if not job_found:
                cancellation_result.update({
                    "status": "not_found",
                    "message": f"Job {job_id} not found in any searched queues"
                })
                logger.warning(f"Job {job_id} not found in any searched queues")
            
            return cancellation_result
            
        except Exception as e:
            error_msg = f"Error cancelling job {job_id}: {str(e)}"
            logger.error(error_msg)
            return {
                "job_id": job_id,
                "cancelled": False,
                "status": "error",
                "error": error_msg,
                "message": "Failed to cancel job due to error"
            }
    
    # Ejecutar la función asíncrona
    try:
        result = asyncio.run(cancel_job())
        return result
    except Exception as e:
        error_msg = f"Failed to execute cancellation: {str(e)}"
        logger.error(error_msg)
        return {
            "job_id": job_id,
            "cancelled": False,
            "status": "execution_error",
            "error": error_msg
        }

# Función asíncrona para uso en contextos async
async def bullmq_cancel_execution_async(job_id: str, queue_name: str = None, redis_url: str = None) -> Dict:
    """
    Versión asíncrona de bullmq_cancel_execution.
    
    Args:
        job_id: ID del trabajo a cancelar
        queue_name: Nombre de la cola donde buscar el trabajo (opcional)
        redis_url: URL de Redis (opcional, usa variable de entorno por defecto)
    
    Returns:
        Dict: Resultado de la cancelación con información del estado
    """
    if redis_url is None:
        redis_url = os.environ.get('REDIS_HOST', 'redis://192.168.1.163:6379')
    
    try:
        # Si no se especifica queue_name, intentar buscar en colas comunes
        search_queues = []
        if queue_name:
            search_queues = [queue_name]
        else:
            search_queues = [
                "test_queue", "priority_test_queue", "activation_test_queue",
                "delay_test_queue", "calibration", "processing", "email",
                "integration_test_queue", "test-delay", "test_custom_id"
            ]
        
        job_found = False
        cancellation_result = {
            "job_id": job_id,
            "cancelled": False,
            "status": "not_found",
            "queue_searched": [],
            "error": None
        }
        
        for queue_name_search in search_queues:
            try:
                queue = Queue(queue_name_search, {"connection": redis_url})
                cancellation_result["queue_searched"].append(queue_name_search)
                
                job = await queue.getJob(job_id)
                
                # Si no se encuentra directamente, buscar en listas de jobs
                if not job:
                    job_states = ["waiting", "delayed", "prioritized", "failed", "completed", "active"]
                    for state in job_states:
                        try:
                            jobs = await queue.getJobs([state], 0, 100)
                            for candidate_job in jobs:
                                if (candidate_job.id == job_id or 
                                    candidate_job.name == job_id or 
                                    (hasattr(candidate_job, 'data') and 
                                     candidate_job.data.get('_custom_job_id') == job_id)):
                                    job = candidate_job
                                    break
                            if job:
                                break
                        except Exception as e:
                            logger.debug(f"Error searching {state} jobs: {e}")
                            continue
                
                if job:
                    job_found = True
                    cancellation_result["found_in_queue"] = queue_name_search
                    
                    job_state = await job.getState()
                    cancellation_result["original_state"] = job_state
                    cancellation_result["internal_job_id"] = job.id
                    
                    if job_state in ["waiting", "delayed", "prioritized", "failed"]:
                        await job.remove()
                        
                        cancellation_result.update({
                            "cancelled": True,
                            "status": "cancelled",
                            "message": f"Job {job_id} cancelled successfully from state '{job_state}'"
                        })
                        
                        logger.info(f"Job {job_id} (internal ID: {job.id}) cancelled successfully from queue '{queue_name_search}' (state: {job_state})")
                        
                    elif job_state in ["active", "completed"]:
                        cancellation_result.update({
                            "cancelled": False,
                            "status": "too_late",
                            "message": f"Job {job_id} cannot be cancelled - already {job_state}"
                        })
                    
                    await queue.close()
                    break
                
                await queue.close()
                
            except Exception as e:
                logger.debug(f"Error searching in queue '{queue_name_search}': {str(e)}")
                continue
        
        if not job_found:
            cancellation_result.update({
                "status": "not_found",
                "message": f"Job {job_id} not found in any searched queues"
            })
        
        return cancellation_result
        
    except Exception as e:
        error_msg = f"Error cancelling job {job_id}: {str(e)}"
        logger.error(error_msg)
        return {
            "job_id": job_id,
            "cancelled": False,
            "status": "error",
            "error": error_msg
        }

def bullmq_cancel_multiple_jobs(job_ids: List[str], queue_name: str = None, redis_url: str = None) -> Dict:
    """
    Cancela múltiples trabajos en lote.
    
    Args:
        job_ids: Lista de IDs de trabajos a cancelar
        queue_name: Nombre de la cola donde buscar (opcional)
        redis_url: URL de Redis (opcional)
    
    Returns:
        Dict: Resumen de cancelaciones realizadas
    """
    results = []
    summary = {
        "total_jobs": len(job_ids),
        "cancelled": 0,
        "not_found": 0,
        "too_late": 0,
        "errors": 0,
        "details": []
    }
    
    for job_id in job_ids:
        try:
            result = bullmq_cancel_execution(job_id, queue_name, redis_url)
            results.append(result)
            
            if result["cancelled"]:
                summary["cancelled"] += 1
            elif result["status"] == "not_found":
                summary["not_found"] += 1
            elif result["status"] == "too_late":
                summary["too_late"] += 1
            else:
                summary["errors"] += 1
                
        except Exception as e:
            summary["errors"] += 1
            results.append({
                "job_id": job_id,
                "cancelled": False,
                "status": "error",
                "error": str(e)
            })
    
    summary["details"] = results
    return summary

# Función de ejemplo/testing
if __name__ == "__main__":
    from .bullmq_sender import bullmq_sender
    import time
    
    print("=== BullMQ Cancel Execution Test ===")
    
    try:
        # 1. Crear un trabajo con delay para poder cancelarlo
        print("1. Creating delayed job...")
        job_id = bullmq_sender(
            worker="cancel_test_queue",
            priority=5,
            delay_sec=10,  # 10 segundos de delay
            message={"test": "cancellation", "data": "This should be cancelled"},
            job_id="test_cancel_job_001"
        )
        
        print(f"✅ Delayed job created with ID: {job_id}")
        
        # 2. Intentar cancelar el trabajo
        print("2. Attempting to cancel the job...")
        cancel_result = bullmq_cancel_execution(job_id, "cancel_test_queue")
        
        print(f"Cancellation result: {cancel_result}")
        
        if cancel_result["cancelled"]:
            print("✅ Job cancelled successfully!")
        else:
            print(f"❌ Job not cancelled: {cancel_result['message']}")
        
        # 3. Test cancelación de trabajo inexistente
        print("3. Testing cancellation of non-existent job...")
        fake_result = bullmq_cancel_execution("fake_job_id_12345")
        print(f"Fake job result: {fake_result}")
        
        # 4. Test cancelación múltiple
        print("4. Testing multiple job cancellation...")
        
        # Crear varios trabajos
        job_ids = []
        for i in range(3):
            job_id = bullmq_sender(
                worker="batch_cancel_test",
                delay_sec=15,
                message={"batch": i},
                job_id=f"batch_job_{i}"
            )
            job_ids.append(job_id)
        
        print(f"Created {len(job_ids)} jobs for batch cancellation")
        
        # Cancelar en lote
        batch_result = bullmq_cancel_multiple_jobs(job_ids, "batch_cancel_test")
        print(f"Batch cancellation result: {batch_result}")
        
    except Exception as e:
        print(f"❌ Test failed: {e}")
