# Archivo: RulesGlobal.py
# Ruta: App\Utilities_Module\RulesGlobal.py
# Descripción: Validador genérico de reglas de negocio
# Autor: Equipo de Desarrollo IGSA
# Fecha: 2025

"""
Módulo de Validación de Reglas Globales.

Este módulo proporciona un validador genérico que evalúa datos contra
reglas definidas en la tabla RulesGlobal de la base de datos.

Características:
- Validación por módulo completo
- Validación de reglas específicas
- Soporte para operadores: >, <, >=, <=, =, !=, BETWEEN
- Soporte para tipos de datos: Number, Date
- Mensajes de error configurables desde BD
"""

import sys
import os
from datetime import datetime, date, timedelta

# Importar consultas SQL
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from Consultas_SQL.Utilities.RulesGlobalSQL import RulesGlobalSQL


class RulesValidator:
    """
    Validador genérico de reglas de negocio.
    
    Esta clase permite validar datos contra reglas definidas en la tabla RulesGlobal.
    Soporta validación por módulo completo o por regla específica.
    
    Ejemplo de uso:
        validator = RulesValidator()
        
        # Validar todo un módulo
        resultado = validator.validar_reglas('FR_FlightRequest', {
            'kilometraje': 350,
            'fecha_salida': '2025-01-20'
        })
        
        # Validar regla específica
        resultado = validator.validar_regla_especifica('KM_MIN', 350)
    """
    
    def __init__(self):
        """Inicializa el validador de reglas."""
        self.sql = RulesGlobalSQL()
    
    # ========================================
    # MÉTODO PRINCIPAL - VALIDAR MÓDULO COMPLETO
    # ========================================
    
    def validar_reglas(self, module_id, datos):
        """
        Valida todas las reglas activas de un módulo específico.
        
        Este método obtiene todas las reglas del módulo desde la BD y
        valida los datos proporcionados contra cada regla.
        
        Args:
            module_id (str): ID del módulo (ej: 'FR_FlightRequest')
            datos (dict): Diccionario con los datos a validar
                         Las keys deben coincidir con los RuleCode
                         
        Returns:
            dict: {
                'valido': bool,           # True si todas las reglas pasan
                'errores': list,          # Lista de mensajes de error
                'reglas_evaluadas': int,  # Cantidad de reglas evaluadas
                'reglas_fallidas': int    # Cantidad de reglas que fallaron
            }
            
        Ejemplo:
            datos = {
                'KM_MIN': 350,              # Para regla de kilometraje mínimo
                'ANTICIP_DIAS': '2025-01-20' # Para regla de anticipación
            }
            resultado = validator.validar_reglas('FR_FlightRequest', datos)
        """
        print(f"\n{'='*80}")
        print(f"🔍 VALIDANDO REGLAS DEL MÓDULO: {module_id}")
        print(f"{'='*80}")
        
        errores = []
        reglas_evaluadas = 0
        reglas_fallidas = 0
        
        # Obtener reglas del módulo desde BD
        resultado_sql = self.sql.obtener_reglas_por_modulo(module_id)
        
        if not resultado_sql['success']:
            print(f"❌ Error al obtener reglas: {resultado_sql['error']}")
            return {
                'valido': False,
                'errores': [f"Error al cargar reglas: {resultado_sql['error']}"],
                'reglas_evaluadas': 0,
                'reglas_fallidas': 0
            }
        
        reglas = resultado_sql['reglas']
        
        if not reglas:
            print(f"ℹ️  No hay reglas configuradas para el módulo {module_id}")
            return {
                'valido': True,
                'errores': [],
                'reglas_evaluadas': 0,
                'reglas_fallidas': 0
            }
        
        print(f"📋 Reglas encontradas: {len(reglas)}")
        
        # Validar cada regla
        for regla in reglas:
            reglas_evaluadas += 1
            rule_code = regla['RuleCode']
            
            print(f"\n{'─'*80}")
            print(f"Evaluando regla: {rule_code} - {regla['RuleName']}")
            
            # Verificar si el dato está presente en los datos proporcionados
            if rule_code not in datos:
                print(f"⚠️  Dato '{rule_code}' no proporcionado en los datos, saltando validación")
                continue
            
            valor_actual = datos[rule_code]
            print(f"   Valor a validar: {valor_actual}")
            print(f"   Operador: {regla['ComparisonOperator']}")
            print(f"   Valor esperado: {regla['RuleValue']}")
            
            # Validar la regla
            resultado_validacion = self._validar_comparacion(
                operator=regla['ComparisonOperator'],
                valor_actual=valor_actual,
                valor_esperado=regla['RuleValue'],
                valor_esperado2=regla['RuleValue2'],
                value_type=regla['ValueType']
            )
            
            if not resultado_validacion['valido']:
                reglas_fallidas += 1
                mensaje_error = regla['MessageIfViolated']
                errores.append(mensaje_error)
                print(f"   ❌ FALLA: {mensaje_error}")
            else:
                print(f"   ✅ PASA")
        
        # Resultado final
        print(f"\n{'='*80}")
        print(f"📊 RESUMEN DE VALIDACIÓN")
        print(f"{'='*80}")
        print(f"   Reglas evaluadas: {reglas_evaluadas}")
        print(f"   Reglas que pasaron: {reglas_evaluadas - reglas_fallidas}")
        print(f"   Reglas que fallaron: {reglas_fallidas}")
        print(f"   Resultado: {'✅ VÁLIDO' if reglas_fallidas == 0 else '❌ INVÁLIDO'}")
        print(f"{'='*80}\n")
        
        return {
            'valido': len(errores) == 0,
            'errores': errores,
            'reglas_evaluadas': reglas_evaluadas,
            'reglas_fallidas': reglas_fallidas
        }
    
    # ========================================
    # MÉTODO SECUNDARIO - VALIDAR REGLA ESPECÍFICA
    # ========================================
    
    def validar_regla_especifica(self, rule_code, valor):
        """
        Valida una regla específica por su código.
        
        Args:
            rule_code (str): Código de la regla (ej: 'KM_MIN')
            valor: Valor a validar (puede ser int, float, str, date)
            
        Returns:
            dict: {
                'valido': bool,
                'mensaje': str (solo si no es válido)
            }
        """
        print(f"\n🔍 Validando regla específica: {rule_code}")
        print(f"   Valor: {valor}")
        
        # Obtener regla desde BD
        resultado_sql = self.sql.obtener_regla_por_codigo(rule_code)
        
        if not resultado_sql['success']:
            print(f"❌ Error: {resultado_sql['error']}")
            return {
                'valido': False,
                'mensaje': resultado_sql['error']
            }
        
        regla = resultado_sql['regla']
        
        # Validar
        resultado_validacion = self._validar_comparacion(
            operator=regla['ComparisonOperator'],
            valor_actual=valor,
            valor_esperado=regla['RuleValue'],
            valor_esperado2=regla['RuleValue2'],
            value_type=regla['ValueType']
        )
        
        if not resultado_validacion['valido']:
            mensaje = regla['MessageIfViolated']
            print(f"❌ FALLA: {mensaje}")
            return {
                'valido': False,
                'mensaje': mensaje
            }
        
        print(f"✅ PASA")
        return {
            'valido': True,
            'mensaje': None
        }
    
    # ========================================
    # MÉTODOS PRIVADOS DE VALIDACIÓN
    # ========================================
    
    def _validar_comparacion(self, operator, valor_actual, valor_esperado, valor_esperado2, value_type):
        """
        Valida una comparación según el operador y tipo de dato.
        
        Args:
            operator (str): Operador (>, <, >=, <=, =, !=, BETWEEN)
            valor_actual: Valor actual a validar
            valor_esperado: Valor esperado de la regla
            valor_esperado2: Segundo valor esperado (para BETWEEN)
            value_type (str): Tipo de dato (Number, Date)
            
        Returns:
            dict: {
                'valido': bool,
                'detalle': str (opcional)
            }
        """
        try:
            # Convertir valores al tipo correcto
            valor_actual_convertido = self._convertir_tipo(valor_actual, value_type)
            valor_esperado_convertido = self._convertir_tipo(valor_esperado, value_type)
            
            # Para BETWEEN, convertir el segundo valor también
            if operator == 'BETWEEN' and valor_esperado2:
                valor_esperado2_convertido = self._convertir_tipo(valor_esperado2, value_type)
            
            # Realizar comparación según operador
            if operator == '>':
                valido = valor_actual_convertido > valor_esperado_convertido
            
            elif operator == '<':
                valido = valor_actual_convertido < valor_esperado_convertido
            
            elif operator == '>=':
                valido = valor_actual_convertido >= valor_esperado_convertido
            
            elif operator == '<=':
                valido = valor_actual_convertido <= valor_esperado_convertido
            
            elif operator == '=':
                valido = valor_actual_convertido == valor_esperado_convertido
            
            elif operator == '!=':
                valido = valor_actual_convertido != valor_esperado_convertido
            
            elif operator == 'BETWEEN':
                if not valor_esperado2:
                    return {
                        'valido': False,
                        'detalle': 'Operador BETWEEN requiere RuleValue2'
                    }
                valido = valor_esperado_convertido <= valor_actual_convertido <= valor_esperado2_convertido
            
            else:
                return {
                    'valido': False,
                    'detalle': f'Operador no soportado: {operator}'
                }
            
            return {
                'valido': valido,
                'detalle': None
            }
            
        except Exception as e:
            return {
                'valido': False,
                'detalle': f'Error en validación: {str(e)}'
            }
    
    def _convertir_tipo(self, valor, value_type):
        """
        Convierte un valor al tipo de dato correcto.
        
        Args:
            valor: Valor a convertir
            value_type (str): Tipo de dato ('Number', 'Date')
            
        Returns:
            Valor convertido (int, float, date, o str)
            
        Raises:
            ValueError: Si no se puede convertir
        """
        if valor is None:
            return None
        
        try:
            if value_type == 'Number':
                # Intentar convertir a float (soporta int y float)
                if isinstance(valor, (int, float)):
                    return float(valor)
                
                # Si es string, limpiar y convertir
                if isinstance(valor, str):
                    # Eliminar espacios, unidades como 'km', 'kg', etc.
                    valor_limpio = valor.strip().lower()
                    valor_limpio = valor_limpio.replace('km', '').replace('kg', '')
                    valor_limpio = valor_limpio.replace('días', '').replace('dias', '')
                    valor_limpio = valor_limpio.strip()
                    return float(valor_limpio)
                
                return float(valor)
            
            elif value_type == 'Date':
                # Si ya es date o datetime
                if isinstance(valor, (date, datetime)):
                    return valor if isinstance(valor, date) else valor.date()
                
                # Si es string, parsear
                if isinstance(valor, str):
                    # Intentar diferentes formatos
                    formatos = [
                        '%Y-%m-%d',           # 2025-01-20
                        '%d/%m/%Y',           # 20/01/2025
                        '%d-%m-%Y',           # 20-01-2025
                        '%Y/%m/%d',           # 2025/01/20
                    ]
                    
                    for formato in formatos:
                        try:
                            return datetime.strptime(valor, formato).date()
                        except ValueError:
                            continue
                    
                    raise ValueError(f"No se pudo parsear fecha: {valor}")
                
                return valor
            
            else:
                # Para otros tipos, devolver como string
                return str(valor)
                
        except Exception as e:
            raise ValueError(f"Error al convertir '{valor}' a tipo '{value_type}': {str(e)}")
    
    # ========================================
    # MÉTODOS DE UTILIDAD
    # ========================================
    
    def obtener_todas_reglas(self):
        """
        Obtiene todas las reglas activas del sistema.
        Útil para debugging o administración.
        
        Returns:
            dict: Resultado de la consulta SQL
        """
        return self.sql.obtener_todas_reglas_activas()
    
    def verificar_regla_existe(self, rule_code):
        """
        Verifica si una regla existe y está activa.
        
        Args:
            rule_code (str): Código de la regla
            
        Returns:
            bool: True si existe y está activa
        """
        resultado = self.sql.verificar_existencia_regla(rule_code)
        return resultado['success'] and resultado['existe']


# ========================================
# FUNCIONES DE VALIDACIÓN ESPECÍFICAS PARA VUELOS
# ========================================

def validar_kilometraje_minimo(kilometraje, km_minimo=300):
    """
    Valida que el kilometraje sea mayor o igual al mínimo establecido.
    
    Args:
        kilometraje (float): Kilometraje del viaje
        km_minimo (float): Kilometraje mínimo requerido (default: 300)
        
    Returns:
        dict: {'valido': bool, 'mensaje': str}
    """
    try:
        km = float(kilometraje)
        if km >= km_minimo:
            return {
                'valido': True,
                'mensaje': None
            }
        else:
            return {
                'valido': False,
                'mensaje': f'El kilometraje debe ser de al menos {km_minimo} km. Actual: {km} km'
            }
    except:
        return {
            'valido': False,
            'mensaje': 'Kilometraje inválido'
        }


def validar_anticipacion_dias(fecha_salida, dias_anticipacion=7):
    """
    Valida que la fecha de salida tenga la anticipación requerida.
    
    Args:
        fecha_salida (str o date): Fecha de salida del viaje
        dias_anticipacion (int): Días de anticipación requeridos (default: 7)
        
    Returns:
        dict: {'valido': bool, 'mensaje': str}
    """
    try:
        # Convertir fecha_salida a date si es string
        if isinstance(fecha_salida, str):
            fecha = datetime.strptime(fecha_salida, '%Y-%m-%d').date()
        elif isinstance(fecha_salida, datetime):
            fecha = fecha_salida.date()
        else:
            fecha = fecha_salida
        
        fecha_minima = date.today() + timedelta(days=dias_anticipacion)
        
        if fecha >= fecha_minima:
            return {
                'valido': True,
                'mensaje': None
            }
        else:
            dias_faltantes = (fecha_minima - fecha).days
            return {
                'valido': False,
                'mensaje': f'Debe solicitar el vuelo con al menos {dias_anticipacion} días de anticipación. Faltan {dias_faltantes} días'
            }
    except:
        return {
            'valido': False,
            'mensaje': 'Fecha de salida inválida'
        }


def validar_equipaje_pasajero(cantidad_maletas, exceso_kg=None):
    """
    Valida que el equipaje cumpla con los límites establecidos.
    
    Reglas:
    - Máximo 1 maleta documentada
    - Peso máximo por maleta: 23 kg
    - Equipaje de mano: 10 kg
    
    Args:
        cantidad_maletas (int): Cantidad de maletas documentadas
        exceso_kg (float): Exceso de equipaje en kg (opcional)
        
    Returns:
        dict: {'valido': bool, 'mensaje': str}
    """
    try:
        errores = []
        
        # Validar cantidad de maletas
        if cantidad_maletas > 1:
            errores.append('Máximo 1 maleta documentada permitida')
        
        # Validar peso
        if exceso_kg is not None and exceso_kg > 23:
            errores.append(f'El peso máximo por maleta es 23 kg. Actual: {exceso_kg} kg')
        
        if errores:
            return {
                'valido': False,
                'mensaje': '; '.join(errores)
            }
        
        return {
            'valido': True,
            'mensaje': None
        }
    except:
        return {
            'valido': False,
            'mensaje': 'Datos de equipaje inválidos'
        }