

import asyncio
import os
from datetime import datetime, timezone
from bullmq import Queue
import logging
from typing import Dict, List, Optional
from .bullmq_sender import bullmq_sender

mongodb_collection_backup = "bullmqJobBackup"

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

def bullmq_backup(db=None, queue_names: List[str] = None, redis_url: str = None, backup_name: str = None) -> Dict:
    """
    Crea un backup completo de todas las colas de BullMQ guardando cada job como documento individual.
    
    Args:
        db: Conexión a MongoDB
        queue_names: Lista de nombres de colas a respaldar (opcional, usa predeterminadas)
        redis_url: URL de Redis (opcional)
        backup_name: Nombre personalizado del backup (opcional)
    
    Returns:
        Dict: Resultado del backup con estadísticas
    """
    if db is None:
        logger.error("Database connection is required for backup")
        return {"status": "error", "message": "Database connection required"}
    
    if redis_url is None:
        redis_url = os.environ.get('REDIS_HOST', 'redis://192.168.1.163:6379')
    
    if queue_names is None:
        # Colas predeterminadas comunes del sistema
        queue_names = [
            "test_queue", "priority_test_queue", "activation_test_queue",
            "delay_test_queue", "calibration", "processing", "email",
            "integration_test_queue", "test-delay", "test_custom_id",
            "cancel_delay_test", "batch_cancel_test"
        ]
    
    backup_id = backup_name or f"backup_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}"
    current_time = datetime.now(timezone.utc)
    
    async def perform_backup():
        total_jobs_backed_up = 0
        successful_queues = 0
        jobs_to_insert = []
        
        for queue_name in queue_names:
            try:
                queue = Queue(queue_name, {"connection": redis_url})
                
                # Estados de jobs a respaldar
                job_states = ["waiting", "delayed", "active", "completed", "failed", "prioritized"]
                
                queue_job_count = 0
                for state in job_states:
                    try:
                        jobs = await queue.getJobs([state], 0, 1000)  # Máximo 1000 jobs por estado
                        
                        for job in jobs:
                            try:
                                # Crear documento individual para cada job
                                job_doc = {
                                    "backup_id": backup_id,
                                    "createAt": current_time,
                                    "updateAt": current_time,
                                    "worker": queue_name,
                                    "job_id": job.id,
                                    "job_name": job.name,
                                    "job_state": state,
                                    "message": job.data,
                                    "delay": getattr(job, 'delay', 0),
                                    "priority": job.opts.get("priority", 5) if job.opts else 5,
                                    "attempts": job.opts.get("attempts", 0) if job.opts else 0,
                                    "timestamp": getattr(job, 'timestamp', None),
                                    "processedOn": getattr(job, 'processedOn', None),
                                    "finishedOn": getattr(job, 'finishedOn', None),
                                    "failedReason": getattr(job, 'failedReason', None),
                                    "progress": getattr(job, 'progress', 0)
                                }
                                
                                jobs_to_insert.append(job_doc)
                                total_jobs_backed_up += 1
                                queue_job_count += 1
                                
                            except Exception as job_error:
                                logger.warning(f"Error backing up job {job.id} from queue '{queue_name}': {job_error}")
                                continue
                        
                        logger.info(f"Prepared {len(jobs)} {state} jobs from queue '{queue_name}' for backup")
                        
                    except Exception as state_error:
                        logger.warning(f"Error backing up {state} jobs from queue '{queue_name}': {state_error}")
                        continue
                
                if queue_job_count > 0:
                    successful_queues += 1
                    
                await queue.close()
                
            except Exception as queue_error:
                logger.error(f"Error backing up queue '{queue_name}': {queue_error}")
                continue
        
        return {
            "total_jobs": total_jobs_backed_up,
            "total_queues": successful_queues,
            "requested_queues": len(queue_names),
            "jobs_to_insert": jobs_to_insert
        }
    
    try:
        # Ejecutar backup asíncrono
        logger.info(f"Starting backup '{backup_id}' for {len(queue_names)} queues...")
        backup_result = asyncio.run(perform_backup())
        
        # Guardar en MongoDB como documentos individuales
        collection = db[mongodb_collection_backup]
        
        # Eliminar backup anterior con el mismo nombre si existe
        deleted_count = collection.delete_many({"backup_id": backup_id}).deleted_count
        if deleted_count > 0:
            logger.info(f"Deleted {deleted_count} existing documents for backup '{backup_id}'")
        
        # Insertar nuevos documentos de jobs si hay datos
        if backup_result["jobs_to_insert"]:
            insert_result = collection.insert_many(backup_result["jobs_to_insert"])
            logger.info(f"Backup '{backup_id}' saved {len(insert_result.inserted_ids)} job documents to MongoDB")
        else:
            logger.warning(f"No jobs found to backup for '{backup_id}'")
        
        return {
            "status": "success",
            "backup_id": backup_id,
            "total_jobs": backup_result["total_jobs"],
            "total_queues": backup_result["total_queues"],
            "requested_queues": backup_result["requested_queues"],
            "created_at": current_time,
            "message": f"Successfully backed up {backup_result['total_jobs']} jobs from {backup_result['total_queues']} queues as individual documents"
        }
        
    except Exception as e:
        error_msg = f"Error creating backup: {str(e)}"
        logger.error(error_msg)
        return {
            "status": "error",
            "backup_id": backup_id,
            "error": error_msg,
            "message": "Failed to create backup"
        }

def bullmq_restore(db=None, backup_id: str = None, redis_url: str = None, 
                   queue_filter: List[str] = None, restore_states: List[str] = None) -> Dict:
    """
    Restaura el estado de las colas de BullMQ desde un backup guardado como documentos individuales.
    
    Args:
        db: Conexión a MongoDB
        backup_id: ID del backup a restaurar (opcional, usa el más reciente)
        redis_url: URL de Redis (opcional)
        queue_filter: Lista de colas específicas a restaurar (opcional, restaura todas)
        restore_states: Estados de jobs a restaurar (opcional, restaura waiting y delayed por defecto)
    
    Returns:
        Dict: Resultado de la restauración con estadísticas
    """
    if db is None:
        logger.error("Database connection is required for restore")
        return {"status": "error", "message": "Database connection required"}
    
    if redis_url is None:
        redis_url = os.environ.get('REDIS_HOST', 'redis://192.168.1.163:6379')
    
    if restore_states is None:
        # Por defecto solo restaurar jobs pendientes y con delay
        restore_states = ["waiting", "delayed"]
    
    try:
        collection = db[mongodb_collection_backup]
        
        # Buscar backup - si no se especifica usar el más reciente
        if not backup_id:
            # Obtener el backup_id más reciente
            latest_backup = collection.find_one(
                {"backup_id": {"$exists": True}},
                sort=[("createAt", -1)]
            )
            if not latest_backup:
                return {
                    "status": "error",
                    "message": "No backups found in database"
                }
            backup_id = latest_backup["backup_id"]
        
        # Verificar que el backup existe
        backup_exists = collection.find_one({"backup_id": backup_id})
        if not backup_exists:
            return {
                "status": "error",
                "message": f"Backup '{backup_id}' not found"
            }
        
        logger.info(f"Restoring backup '{backup_id}'...")
        
        async def perform_restore():
            restore_stats = {
                "backup_id": backup_id,
                "restored_at": datetime.now(timezone.utc),
                "total_jobs_restored": 0,
                "queues_restored": 0,
                "queue_details": {},
                "skipped_jobs": 0,
                "errors": []
            }
            
            # Construir filtros de consulta
            query_filter = {"backup_id": backup_id}
            
            # Filtrar por estados específicos
            if restore_states:
                query_filter["job_state"] = {"$in": restore_states}
            
            # Filtrar por colas específicas
            if queue_filter:
                query_filter["worker"] = {"$in": queue_filter}
            
            # Obtener jobs del backup
            backup_jobs = collection.find(query_filter).sort("createAt", 1)
            
            queue_stats = {}
            
            for job_doc in backup_jobs:
                queue_name = job_doc.get("worker")
                
                if queue_name not in queue_stats:
                    queue_stats[queue_name] = {
                        "jobs_restored": 0,
                        "jobs_skipped": 0,
                        "status": "no_jobs"
                    }
                
                try:
                    # Extraer datos del job
                    job_data = job_doc.get("message", {})
                    priority = job_doc.get("priority", 5)
                    delay = job_doc.get("delay", 0)
                    original_job_id = job_doc.get("job_id")
                    job_state = job_doc.get("job_state", "unknown")
                    
                    # Agregar metadata de restauración
                    restore_data = {
                        **job_data,
                        "_restored_from_backup": backup_id,
                        "_restored_at": datetime.now(timezone.utc).isoformat(),
                        "_original_job_id": original_job_id,
                        "_original_state": job_state
                    }
                    
                    # Configurar delay en segundos (convertir de milisegundos)
                    delay_sec = delay // 1000 if delay > 0 else 0
                    
                    # Restaurar job usando bullmq_sender
                    restored_job_id = bullmq_sender(
                        worker=queue_name,
                        priority=priority,
                        delay_sec=delay_sec,
                        message=restore_data,
                        redis_url=redis_url
                    )
                    
                    if restored_job_id:
                        queue_stats[queue_name]["jobs_restored"] += 1
                        queue_stats[queue_name]["status"] = "success"
                        restore_stats["total_jobs_restored"] += 1
                        
                        logger.debug(f"Restored job {original_job_id} as {restored_job_id} in queue '{queue_name}'")
                    else:
                        queue_stats[queue_name]["jobs_skipped"] += 1
                        restore_stats["skipped_jobs"] += 1
                        
                except Exception as job_restore_error:
                    error_msg = f"Error restoring job {job_doc.get('job_id', 'unknown')} in queue '{queue_name}': {job_restore_error}"
                    logger.error(error_msg)
                    restore_stats["errors"].append(error_msg)
                    
                    if queue_name in queue_stats:
                        queue_stats[queue_name]["jobs_skipped"] += 1
                    restore_stats["skipped_jobs"] += 1
            
            # Calcular estadísticas finales
            for queue_name, stats in queue_stats.items():
                restore_stats["queue_details"][queue_name] = stats
                if stats["jobs_restored"] > 0:
                    restore_stats["queues_restored"] += 1
                    
                logger.info(f"Queue '{queue_name}': {stats['jobs_restored']} jobs restored, {stats['jobs_skipped']} skipped")
            
            return restore_stats
        
        # Ejecutar restauración
        restore_result = asyncio.run(perform_restore())
        
        # Guardar log de restauración en MongoDB
        restore_log = {
            "restore_id": f"restore_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}",
            "backup_id": backup_id,
            **restore_result
        }
        
        collection.insert_one(restore_log)
        
        logger.info(f"Restore completed: {restore_result['total_jobs_restored']} jobs restored to {restore_result['queues_restored']} queues")
        
        return {
            "status": "success" if restore_result["total_jobs_restored"] > 0 else "no_jobs",
            "backup_id": backup_id,
            "total_jobs_restored": restore_result["total_jobs_restored"],
            "queues_restored": restore_result["queues_restored"],
            "skipped_jobs": restore_result["skipped_jobs"],
            "errors_count": len(restore_result["errors"]),
            "queue_details": restore_result["queue_details"],
            "restored_at": restore_result["restored_at"],
            "message": f"Successfully restored {restore_result['total_jobs_restored']} jobs to {restore_result['queues_restored']} queues"
        }
        
    except Exception as e:
        error_msg = f"Error during restore: {str(e)}"
        logger.error(error_msg)
        return {
            "status": "error",
            "backup_id": backup_id,
            "error": error_msg,
            "message": "Failed to restore backup"
        }

# Funciones auxiliares
def list_backups(db=None) -> List[Dict]:
    """
    Lista todos los backups disponibles en la base de datos agregando estadísticas.
    
    Args:
        db: Conexión a MongoDB
    
    Returns:
        List[Dict]: Lista de backups disponibles con estadísticas
    """
    if db is None:
        return []
    
    try:
        collection = db[mongodb_collection_backup]
        
        # Usar agregación para obtener estadísticas por backup_id
        pipeline = [
            {
                "$group": {
                    "_id": "$backup_id",
                    "total_jobs": {"$sum": 1},
                    "total_queues": {"$addToSet": "$worker"},
                    "created_at": {"$min": "$createAt"},
                    "latest_update": {"$max": "$updateAt"},
                    "job_states": {"$addToSet": "$job_state"}
                }
            },
            {
                "$project": {
                    "backup_id": "$_id",
                    "total_jobs": 1,
                    "total_queues": {"$size": "$total_queues"},
                    "queue_names": "$total_queues",
                    "created_at": 1,
                    "latest_update": 1,
                    "job_states": 1,
                    "_id": 0
                }
            },
            {
                "$sort": {"created_at": -1}
            }
        ]
        
        backups = list(collection.aggregate(pipeline))
        
        logger.info(f"Found {len(backups)} backups in database")
        return backups
        
    except Exception as e:
        logger.error(f"Error listing backups: {e}")
        return []

def delete_backup(db=None, backup_id: str = None) -> Dict:
    """
    Elimina un backup específico de la base de datos.
    
    Args:
        db: Conexión a MongoDB
        backup_id: ID del backup a eliminar
    
    Returns:
        Dict: Resultado de la eliminación
    """
    if db is None or not backup_id:
        return {"status": "error", "message": "Database connection and backup_id required"}
    
    try:
        collection = db[mongodb_collection_backup]
        result = collection.delete_many({"backup_id": backup_id})
        
        if result.deleted_count > 0:
            logger.info(f"Deleted backup '{backup_id}' ({result.deleted_count} documents)")
            return {
                "status": "success",
                "backup_id": backup_id,
                "deleted_count": result.deleted_count,
                "message": f"Successfully deleted backup '{backup_id}' with {result.deleted_count} jobs"
            }
        else:
            return {
                "status": "not_found",
                "backup_id": backup_id,
                "message": f"Backup '{backup_id}' not found"
            }
    except Exception as e:
        error_msg = f"Error deleting backup '{backup_id}': {str(e)}"
        logger.error(error_msg)
        return {
            "status": "error",
            "backup_id": backup_id,
            "error": error_msg
        }

def bullmq_delete_backup(backup_id: str, db=None) -> Dict:
    """
    Elimina completamente un backup y permite recrearlo.
    Esta es la función principal recomendada para eliminar backups.
    
    Args:
        backup_id: ID del backup a eliminar
        db: Conexión a MongoDB
    
    Returns:
        Dict: Resultado de la eliminación con estadísticas detalladas
    """
    if db is None:
        logger.error("Database connection is required")
        return {"status": "error", "message": "Database connection required"}
        
    if not backup_id:
        return {"status": "error", "message": "backup_id is required"}
    
    try:
        collection = db[mongodb_collection_backup]
        
        # Primero obtener estadísticas del backup antes de eliminarlo
        stats_pipeline = [
            {"$match": {"backup_id": backup_id}},
            {
                "$group": {
                    "_id": "$backup_id",
                    "total_jobs": {"$sum": 1},
                    "queues": {"$addToSet": "$worker"},
                    "job_states": {"$addToSet": "$job_state"},
                    "created_at": {"$min": "$createAt"}
                }
            }
        ]
        
        backup_stats = list(collection.aggregate(stats_pipeline))
        
        if not backup_stats:
            return {
                "status": "not_found",
                "backup_id": backup_id,
                "message": f"Backup '{backup_id}' not found in database"
            }
        
        stats = backup_stats[0]
        
        # Eliminar todos los documentos del backup
        delete_result = collection.delete_many({"backup_id": backup_id})
        
        logger.info(f"Deleted backup '{backup_id}': {delete_result.deleted_count} documents removed")
        
        return {
            "status": "success",
            "backup_id": backup_id,
            "deleted_count": delete_result.deleted_count,
            "backup_info": {
                "total_jobs": stats["total_jobs"],
                "total_queues": len(stats["queues"]),
                "queue_names": stats["queues"],
                "job_states": stats["job_states"],
                "created_at": stats["created_at"]
            },
            "message": f"Successfully deleted backup '{backup_id}' containing {stats['total_jobs']} jobs from {len(stats['queues'])} queues"
        }
        
    except Exception as e:
        error_msg = f"Error deleting backup '{backup_id}': {str(e)}"
        logger.error(error_msg)
        return {
            "status": "error",
            "backup_id": backup_id,
            "error": error_msg,
            "message": "Failed to delete backup"
        }

# Función de ejemplo/testing con base de datos real
if __name__ == "__main__":
    # Ejemplo de uso con base de datos real
    try:
        # Importar conexión real a MongoDB
        from tredops_eval.connect import db
        
        print("=== BullMQ Backup Example (Individual Documents) ===")
        
        if db is None:
            print("❌ Error: No se pudo conectar a MongoDB")
            print("Asegúrate de que la base de datos esté configurada correctamente")
            exit(1)
        
        print(f"✅ Conectado a MongoDB - Base: {db.name}")
        
        # 1. Crear algunos jobs de prueba
        print("\\n1. Creando jobs de prueba...")
        from bullmq_sender import bullmq_sender
        
        test_queues = ["example_queue_1", "example_queue_2"]
        job_ids = []
        
        for i, queue_name in enumerate(test_queues):
            job_id = bullmq_sender(
                worker=queue_name,
                priority=5,
                delay_sec=2,  # Delay para testing
                message={
                    "example": True,
                    "test_data": f"Example job {i}",
                    "timestamp": datetime.now(timezone.utc).isoformat()
                }
            )
            job_ids.append(job_id)
            print(f"  Job creado: {job_id} en '{queue_name}'")
        
        # Esperar que se registren
        import time
        time.sleep(1)
        
        # 2. Eliminar backup anterior si existe
        print("\\n2. Limpiando backup anterior...")
        delete_result = bullmq_delete_backup("example_backup", db=db)
        if delete_result["status"] == "success":
            print(f"✅ Backup anterior eliminado: {delete_result['deleted_count']} documentos")
        elif delete_result["status"] == "not_found":
            print("ℹ️  No hay backup anterior para limpiar")
        
        # 3. Hacer backup
        print("\\n3. Creando backup...")
        backup_result = bullmq_backup(
            db=db,
            queue_names=test_queues,
            backup_name="example_backup"
        )
        
        print(f"Resultado: {backup_result}")
        
        if backup_result["status"] == "success":
            print(f"✅ Backup creado exitosamente!")
            print(f"   - ID: {backup_result['backup_id']}")
            print(f"   - Jobs: {backup_result['total_jobs']}")
            print(f"   - Queues: {backup_result['total_queues']}")
        
        # 4. Verificar en base de datos
        print("\\n4. Verificando en base de datos...")
        collection = db[mongodb_collection_backup]
        
        backup_docs = list(collection.find({"backup_id": "example_backup"}))
        if backup_docs:
            print(f"✅ Backup encontrado en MongoDB: {len(backup_docs)} documentos")
            for doc in backup_docs[:3]:  # Mostrar primeros 3
                print(f"   - Job {doc['job_id']} en '{doc['worker']}' (estado: {doc['job_state']})")
        else:
            print("❌ Backup no encontrado en MongoDB")
        
        # 5. Listar todos los backups
        print("\\n5. Listando backups existentes...")
        backups = list_backups(db)
        print(f"Total backups: {len(backups)}")
        for backup in backups[:3]:  # Mostrar primeros 3
            print(f"  - {backup['backup_id']} ({backup['total_jobs']} jobs, {backup['total_queues']} queues)")
        
        print("\\n✅ Ejemplo completado. Revisa tu base de datos MongoDB!")
        print(f"   Colección: {mongodb_collection_backup}")
        print("   Estructura: Un documento por cada job individual")
        
    except ImportError:
        print("❌ Error: No se pudo importar la conexión a la base de datos")
        print("Asegúrate de que 'tredops_eval.connect' esté disponible")
    except Exception as e:
        print(f"❌ Error ejecutando ejemplo: {e}")
        import traceback
        traceback.print_exc()



