# Archivo: MailManagement.py
# Ruta: src\App\Utilities_module\MailManagement.py
# Descripción: Logica del Módulo universal para el envío de correos electrónicos utilizando Flask-Mail.
# Autor: Equipo de Desarrollo IGSA
# Fecha: 2025

import os
import json
import logging
from flask_login import current_user
from datetime import datetime
from flask import current_app, render_template
from flask_mail import Mail, Message
from Consultas_SQL.Utilities.MailManagementSQL import obtener_destinatarios_mail, validar_mail_list_existe, guardar_log_envio, obtener_subject_de_mail_list

# Configurar logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def enviar_correo_universal(
    template_path,
    asunto=None,
    template_data=None,
    mail_list_id=None,
    destinatarios_adicionales=None,
    archivos_adjuntos=None,
    configuracion_adicional=None
):
    """
    Función universal para envío de correos electrónicos utilizando Flask-Mail.
    
    Args:
        template_path (str): Ruta del template HTML relativa a templates/
        asunto (str): Asunto del correo
        template_data (dict, optional): Datos para inyectar en el template
        mail_list_id (int, optional): ID de la lista de correos en Util_MailListHead
        destinatarios_adicionales (dict, optional): Destinatarios adicionales
            {'TO': ['email1@test.com'], 'CC': ['email2@test.com'], 'BCC': ['email3@test.com']}
        archivos_adjuntos (list, optional): Lista de archivos adjuntos
            [{'tipo': 'local', 'ruta': 'path/file.pdf', 'nombre': 'archivo.pdf'},
             {'tipo': 'url', 'ruta': 'https://...', 'nombre': 'archivo_web.pdf'}]
        configuracion_adicional (dict, optional): Configuraciones adicionales
        
    Returns:
        dict: Resultado del envío
    """
    
    # Inicializar resultado
    resultado = {
        'success': False,
        'mensaje': '',
        'destinatarios': {'TO': [], 'CC': [], 'BCC': [], 'total_destinatarios': 0},
        'errores': [],
        'warnings': [],
        'mail_list_id': mail_list_id,
        'template_usado': template_path,
        'archivos_enviados': [],
        'tiempo_ejecucion': None
    }
    
    inicio_tiempo = datetime.now()
    
    try:
        # Validar parámetros básicos
        if not template_path:
            resultado['errores'].append('template_path es requerido')
            resultado['mensaje'] = 'Parámetros de entrada inválidos'
            return resultado
            
        if not asunto:
            # Si no recibes asunto, busca en BD si hay MailListID
            if mail_list_id:
                asunto_bd = obtener_subject_de_mail_list(mail_list_id)
                asunto = asunto_bd or '[Sin Asunto]'
                if not asunto_bd:
                    resultado['warnings'].append('No se encontró asunto en la BD, usando "[Sin Asunto]"')
                else:
                    asunto = '[Sin Asunto]'
                    resultado['warnings'].append('No se proporcionó asunto ni mail_list_id, usando "[Sin Asunto]"')
                return resultado
        
        # Inicializar configuración
        config = current_app.config
        template_data = template_data or {}
        archivos_adjuntos = archivos_adjuntos or []
        configuracion_adicional = configuracion_adicional or {}
        destinatarios_adicionales = destinatarios_adicionales or {}
        
        logger.info(f"Iniciando envío de correo para template: {template_path}")
        
        # Inicializar destinatarios finales
        destinatarios_finales = {'TO': [], 'CC': [], 'BCC': []}
        
        # 1. Obtener destinatarios de la base de datos (si se proporciona mail_list_id)
        if mail_list_id:
            logger.info(f"Obteniendo destinatarios de MailListID: {mail_list_id}")
            
            # Validar que existe el MailListID
            validacion = validar_mail_list_existe(mail_list_id)
            if not validacion['success']:
                resultado['errores'].append(f"Error al validar MailListID: {validacion['mensaje']}")
                resultado['mensaje'] = 'Error en validación de MailListID'
                return resultado
            
            if not validacion['existe']:
                resultado['warnings'].append(f"MailListID {mail_list_id} no existe en la base de datos")
                logger.warning(f"MailListID {mail_list_id} no existe")
            elif not validacion['activo']:
                resultado['warnings'].append(f"MailListID {mail_list_id} está inactivo")
                logger.warning(f"MailListID {mail_list_id} está inactivo")
            else:
                # Obtener destinatarios de la BD
                destinatarios_bd = obtener_destinatarios_mail(mail_list_id)
                if destinatarios_bd['success']:
                    destinatarios_finales['TO'].extend(destinatarios_bd['TO'])
                    destinatarios_finales['CC'].extend(destinatarios_bd['CC'])
                    destinatarios_finales['BCC'].extend(destinatarios_bd['BCC'])
                else:
                    resultado['warnings'].append(f"Error al obtener destinatarios de BD: {destinatarios_bd['mensaje']}")
        
        # 2. Agregar destinatarios adicionales
        if destinatarios_adicionales:
            for tipo in ['TO', 'CC', 'BCC']:
                if tipo in destinatarios_adicionales and destinatarios_adicionales[tipo]:
                    for email in destinatarios_adicionales[tipo]:
                        if email and '@' in email and email not in destinatarios_finales[tipo]:
                            destinatarios_finales[tipo].append(email)
        
        # 3. Validar que hay al menos un destinatario TO
        if not destinatarios_finales['TO']:
            if not mail_list_id and not destinatarios_adicionales:
                resultado['errores'].append('Debe proporcionar mail_list_id o destinatarios_adicionales')
                resultado['mensaje'] = 'No hay destinatarios para enviar el correo'
                return resultado
            else:
                resultado['warnings'].append('No se encontraron destinatarios TO válidos')
                resultado['success'] = True
                resultado['mensaje'] = 'Proceso completado con advertencias - no hay destinatarios TO'
                return resultado
        
        # Actualizar resultado con destinatarios finales
        resultado['destinatarios'] = {
            'TO': destinatarios_finales['TO'],
            'CC': destinatarios_finales['CC'],
            'BCC': destinatarios_finales['BCC'],
            'total_destinatarios': len(destinatarios_finales['TO']) + len(destinatarios_finales['CC']) + len(destinatarios_finales['BCC'])
        }
        
        # 4. Validar y procesar archivos adjuntos
        archivos_validados = []
        for archivo in archivos_adjuntos:
            resultado_validacion = validar_archivo_adjunto(archivo)
            if resultado_validacion['valido']:
                archivos_validados.append(archivo)
                resultado['archivos_enviados'].append(archivo.get('nombre', 'archivo'))
            else:
                resultado['warnings'].append(f"Archivo no válido: {resultado_validacion['mensaje']}")
        
        # 5. Preparar datos para el template
        datos_template = preparar_datos_template(template_data, config)
        
        # 6. Validar y renderizar template
        try:
            cuerpo_html = render_template(template_path, **datos_template)
        except Exception as e:
            resultado['errores'].append(f"Error al renderizar template '{template_path}': {str(e)}")
            resultado['mensaje'] = 'Error en renderizado de template'
            return resultado
        
        # 7. Crear y enviar mensaje
        try:
            # Crear mensaje con remitente desde config
            msg = Message(
                subject=asunto,
                recipients=destinatarios_finales['TO'],
                cc=destinatarios_finales['CC'] if destinatarios_finales['CC'] else None,
                bcc=destinatarios_finales['BCC'] if destinatarios_finales['BCC'] else None,
                html=cuerpo_html,
                sender=config.get('MAIL_DEFAULT_SENDER')
            )
            
            # Agregar archivos adjuntos
            for archivo in archivos_validados:
                try:
                    resultado_adjunto = agregar_archivo_adjunto(msg, archivo)
                    if not resultado_adjunto['success']:
                        resultado['warnings'].append(f"Error al adjuntar archivo: {resultado_adjunto['mensaje']}")
                except Exception as e:
                    resultado['warnings'].append(f"Error al adjuntar archivo {archivo.get('nombre', 'desconocido')}: {str(e)}")
            
            # Enviar correo
            mail = Mail(current_app)
            mail.send(msg)
            
            # Éxito
            resultado['success'] = True
            resultado['mensaje'] = f'Correo enviado exitosamente a {resultado["destinatarios"]["total_destinatarios"]} destinatarios'
            
            logger.info(f"Correo enviado exitosamente. Template: {template_path}")
            
        except Exception as e:
            resultado['errores'].append(f"Error al enviar correo: {str(e)}")
            resultado['mensaje'] = 'Error en envío de correo'
            logger.error(f"Error al enviar correo: {str(e)}")
            return resultado
            
    except Exception as e:
        resultado['errores'].append(f"Error general: {str(e)}")
        resultado['mensaje'] = 'Error general en el proceso'
        logger.error(f"Error general en enviar_correo_universal: {str(e)}")
        
    finally:
        # Calcular tiempo de ejecución
        fin_tiempo = datetime.now()
        resultado['tiempo_ejecucion'] = str(fin_tiempo - inicio_tiempo)
        
        # Log final
        if resultado['success']:
            logger.info(f"Proceso completado exitosamente para MailListID: {mail_list_id}")
        else:
            logger.error(f"Proceso falló para MailListID: {mail_list_id}")

        # Determina el usuario que envió (si aplica)
        try:
            sent_by = current_user.email
        except Exception:
            sent_by = 'SYSTEM'

        # Llama a la función que guarda el log (no interrumpe el flujo si falla)
        try:
            guardar_log_envio(
                mail_list_id=mail_list_id,
                subject=asunto,
                recipients_to=";".join(destinatarios_finales.get('TO', [])),
                recipients_cc=";".join(destinatarios_finales.get('CC', [])),
                recipients_bcc=";".join(destinatarios_finales.get('BCC', [])),
                template_used=template_path,
                template_data=json.dumps(template_data),
                files_sent=json.dumps(resultado.get('archivos_enviados', [])),
                sent_by=sent_by,
                status='ENVIADO' if resultado['success'] else 'FALLIDO',
                error_msg="; ".join(resultado.get('errores', [])) if resultado.get('errores') else None
            )
        except Exception as e:
            logger.error(f"Error al guardar log de correo: {str(e)}")
    
    return resultado


def validar_archivo_adjunto(archivo):
    """
    Valida si un archivo adjunto es válido (local o URL).
    
    Args:
        archivo (dict): Diccionario con información del archivo
                       {'tipo': 'local', 'ruta': '/path/to/file', 'nombre': 'archivo.pdf'}
                       {'tipo': 'url', 'ruta': 'https://...', 'nombre': 'archivo.pdf'}
    
    Returns:
        dict: Resultado de la validación
              {'valido': True/False, 'mensaje': 'descripción', 'tipo': 'local/url'}
    """
    
    if not isinstance(archivo, dict):
        return {'valido': False, 'mensaje': 'Archivo debe ser un diccionario', 'tipo': None}
    
    ruta = archivo.get('ruta')
    tipo = archivo.get('tipo', 'local')
    
    if not ruta:
        return {'valido': False, 'mensaje': 'Ruta del archivo es requerida', 'tipo': tipo}
    
    try:
        if tipo == 'local':
            # Validar archivo local
            if os.path.exists(ruta) and os.path.isfile(ruta) and os.access(ruta, os.R_OK):
                return {'valido': True, 'mensaje': 'Archivo local válido', 'tipo': 'local'}
            else:
                return {'valido': False, 'mensaje': f'Archivo local no encontrado o no accesible: {ruta}', 'tipo': 'local'}
        
        elif tipo == 'url':
            # Validar URL
            import urllib.parse
            parsed_url = urllib.parse.urlparse(ruta)
            if parsed_url.scheme in ['http', 'https'] and parsed_url.netloc:
                return {'valido': True, 'mensaje': 'URL válida', 'tipo': 'url'}
            else:
                return {'valido': False, 'mensaje': f'URL inválida: {ruta}', 'tipo': 'url'}
        
        else:
            return {'valido': False, 'mensaje': f'Tipo de archivo no válido: {tipo}. Use "local" o "url"', 'tipo': tipo}
            
    except Exception as e:
        return {'valido': False, 'mensaje': f'Error al validar archivo: {str(e)}', 'tipo': tipo}


def agregar_archivo_adjunto(msg, archivo):
    """
    Agrega un archivo adjunto al mensaje de correo.
    
    Args:
        msg (Message): Mensaje de Flask-Mail
        archivo (dict): Información del archivo
        
    Returns:
        dict: Resultado de la operación
    """
    
    try:
        tipo = archivo.get('tipo', 'local')
        ruta = archivo.get('ruta')
        nombre = archivo.get('nombre', 'archivo_adjunto')
        mimetype = archivo.get('mimetype', 'application/octet-stream')
        
        if tipo == 'local':
            # Archivo local
            with open(ruta, 'rb') as fp:
                msg.attach(nombre, mimetype, fp.read())
            return {'success': True, 'mensaje': f'Archivo local adjuntado: {nombre}'}
        
        elif tipo == 'url':
            # Archivo desde URL
            import urllib.request
            try:
                with urllib.request.urlopen(ruta) as response:
                    data = response.read()
                    msg.attach(nombre, mimetype, data)
                return {'success': True, 'mensaje': f'Archivo URL adjuntado: {nombre}'}
            except Exception as e:
                return {'success': False, 'mensaje': f'Error al descargar archivo desde URL: {str(e)}'}
        
        else:
            return {'success': False, 'mensaje': f'Tipo de archivo no soportado: {tipo}'}
            
    except Exception as e:
        return {'success': False, 'mensaje': f'Error al adjuntar archivo: {str(e)}'}


def preparar_datos_template(template_data, config):
    """
    Prepara los datos que se enviarán al template, incluyendo variables globales.
    
    Args:
        template_data (dict): Datos específicos del template
        config (Config): Configuración de Flask
        
    Returns:
        dict: Datos completos para el template
    """
    
    # Variables globales siempre disponibles (opcionales)
    datos_completos = {
        'año_actual': datetime.now().year,
        'fecha_actual': datetime.now().strftime('%d de %B, %Y'),
        'empresa': 'IGSA',
        'empresa_completa': 'Integradora de Servicios Avanzados',
        'url_base': f"{config.get('SCHEME', 'http')}://{config.get('Host', 'localhost')}",
        'logo_empresa': f"{config.get('SCHEME', 'http')}://{config.get('Host', 'localhost')}/static/img/logo.png",
        'soporte_email': 'soporte@igsa.com',
        'ventas_email': 'ventas@igsa.com',
        'telefono_principal': '+52 (999) 123-4567'
    }
    
    # Combinar con datos específicos del template
    if template_data:
        datos_completos.update(template_data)
    
    return datos_completos


def obtener_info_envio(mail_list_id):
    """
    Obtiene información resumida sobre el envío de correo.
    
    Args:
        mail_list_id (int): ID de la lista de correos
        
    Returns:
        dict: Información del envío
    """
    
    try:
        # Obtener información básica
        validacion = validar_mail_list_existe(mail_list_id)
        destinatarios = obtener_destinatarios_mail(mail_list_id)
        
        info = {
            'mail_list_id': mail_list_id,
            'existe': validacion.get('existe', False),
            'activo': validacion.get('activo', False),
            'subject': validacion.get('subject'),
            'module_id': validacion.get('module_id'),
            'total_destinatarios': destinatarios.get('total_destinatarios', 0),
            'destinatarios_por_tipo': {
                'TO': len(destinatarios.get('TO', [])),
                'CC': len(destinatarios.get('CC', [])),
                'BCC': len(destinatarios.get('BCC', []))
            },
            'success': True,
            'mensaje': 'Información obtenida correctamente'
        }
        
        return info
        
    except Exception as e:
        return {
            'success': False,
            'mensaje': f'Error al obtener información: {str(e)}'
        }


def generar_preview_correo(mail_list_id, template_path, template_data=None):
    """
    Genera una vista previa del correo sin enviarlo.
    
    Args:
        mail_list_id (int): ID de la lista de correos
        template_path (str): Ruta del template HTML
        template_data (dict, optional): Datos para el template
        
    Returns:
        dict: Preview del correo
    """
    
    try:
        config = current_app.config
        template_data = template_data or {}
        
        # Preparar datos del template
        datos_template = preparar_datos_template(template_data, config)
        
        # Obtener información del envío
        info_envio = obtener_info_envio(mail_list_id)
        
        # Renderizar template
        cuerpo_html = render_template(template_path, **datos_template)
        
        preview = {
            'html_content': cuerpo_html,
            'info_envio': info_envio,
            'datos_template': datos_template,
            'template_path': template_path,
            'success': True,
            'mensaje': 'Preview generado correctamente'
        }
        
        return preview
        
    except Exception as e:
        return {
            'success': False,
            'mensaje': f'Error al generar preview: {str(e)}'
        }
        
