"""
Copista Inteligente Multi-Hilo S3
=================================
Módulo para copiar vastas colecciones de archivos a través de S3 o entre S3
evitando iniciar Spark. Implementa Multithreading C-Bindings de Python
para paralelizar llamadas Boto3.
"""
import logging
import re
from urllib.parse import urlparse
from concurrent.futures import ThreadPoolExecutor, as_completed

logger = logging.getLogger(__name__)

class AgS3SmartCopier:
    """
    Herramienta asíncrona parametrizable para clonado masivo de objetos en S3.
    Equipada con los filtros dinámicos estructurales de Antigravity LITE.
    """
    
    def __init__(self, s3_client=None):
        if s3_client is None:
            import boto3
            self.s3_client = boto3.client('s3')
        else:
            self.s3_client = s3_client

    def _parse_s3_path(self, s3_path: str):
        url = urlparse(s3_path)
        return url.netloc, url.path.lstrip('/')

    def _should_include(self, key_name: str, starts_with=None, ends_with=None, contains=None, match_regex=None) -> bool:
        """ Evalúa si la llave S3 cumple los caprichos del desarrollador """
        filename = key_name.split('/')[-1] if '/' in key_name else key_name
        
        # Ignorar directorios nulos de Hadoop
        if filename == "" or filename.endswith("_SUCCESS"):
            return False
            
        if starts_with and not filename.startswith(starts_with):
            return False
        if ends_with and not filename.endswith(ends_with):
            return False
        if contains and contains not in filename:
            return False
        if match_regex and not re.search(match_regex, filename):
            return False
            
        return True

    def copy_path(self, origin_path: str, dest_path: str, starts_with=None, ends_with=None, 
                  contains=None, match_regex=None, max_workers=10):
        """
        Escanea recursivamente los archivos de S3 de la ruta de origen, los filtra matemáticamente
        y ejecuta enjambres asíncronos para copiarlos ultra rápido a un destino S3 distinto.
        
        :param origin_path: Directorio origen ("s3://bucket-1/raw/")
        :param dest_path: Directorio destino ("s3://bucket-2/curated/")
        :param max_workers: Threads paralelos de Python a usar.
        """
        src_bucket, src_prefix = self._parse_s3_path(origin_path)
        dst_bucket, dst_prefix = self._parse_s3_path(dest_path)
        
        # Formateos lógicos
        if src_prefix and not src_prefix.endswith('/'): src_prefix += '/'
        if dst_prefix and not dst_prefix.endswith('/'): dst_prefix += '/'

        logger.info(f"Iniciando escaneo masivo recursivo en {origin_path}")
        
        paginator = self.s3_client.get_paginator('list_objects_v2')
        pages = paginator.paginate(Bucket=src_bucket, Prefix=src_prefix)
        
        # Construir matriz de operaciones (lista cruzada Origen-Destino)
        tasks_copy = []
        for page in pages:
            if 'Contents' not in page: continue
            
            for obj in page['Contents']:
                object_key = obj['Key']
                
                if self._should_include(object_key, starts_with, ends_with, contains, match_regex):
                    # Preservar "carpetas" internas (Ruta relativa)
                    relpath = object_key[len(src_prefix):] if object_key.startswith(src_prefix) else object_key.split('/')[-1]
                    new_dst_key = dst_prefix + relpath
                    
                    tasks_copy.append({
                        'src_bucket': src_bucket,
                        'src_key': object_key,
                        'dst_bucket': dst_bucket,
                        'dst_key': new_dst_key
                    })

        total = len(tasks_copy)
        if total == 0:
            logger.warning("Ningún archivo sobrevivió tu filtro mágico. Abortando copia.")
            return

        logger.info(f"Filtro aplicado. Clonando {total} archivos detectados (Hilos en CPU: {max_workers})...")
        
        # Multithreading Súper Agresivo
        success_count = 0
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            futuros = []
            for t in tasks_copy:
                copy_source = {'Bucket': t['src_bucket'], 'Key': t['src_key']}
                call = executor.submit(
                    self.s3_client.copy_object,
                    CopySource=copy_source, Bucket=t['dst_bucket'], Key=t['dst_key']
                )
                futuros.append(call)
                
            for ft in as_completed(futuros):
                try:
                    ft.result()
                    success_count += 1
                except Exception as ex:
                    logger.error(f"Falla de Thread copiando Objeto: {ex}")

        logger.info(f"Copia Masiva Completada. [{success_count}/{total}] Objetos transferidos a {dest_path}")
