"""
Procesador Estadístico - MVP v1.0

Maneja el procesamiento de estadísticas por períodos de 15 minutos,
calculando promedios, máximos, mínimos y desvíos.
"""

import os
import logging
import time
from datetime import datetime, timedelta
from collections import defaultdict
from ..base import BaseProcessor
from ...config.constants import (
    STATISTICS_BATCH_SIZE,
    STATISTICS_SLEEP,
    PROCESS_INTERVAL
)
from .integration import IntegrationCalculator

logger = logging.getLogger(__name__)

class StatisticsProcessor(BaseProcessor):
    def __init__(self, db_name):
        super().__init__(db_name)
        self.calculator = IntegrationCalculator()
        self.batch_size = STATISTICS_BATCH_SIZE
        self.current_periods = {}
        self.metrics = {
            'processed_measurements': 0,
            'generated_records': 0,
            'active_periods': 0
        }
        self.process_interval = PROCESS_INTERVAL
        self.max_retries = 3
        self.retry_delay = 5
        self.configs = {}  # Inicializar el diccionario de configuraciones
        self._load_configurations()  # Cargar configuraciones al inicio
        logger.info("StatisticsProcessor inicializado")

    def run(self):
        """Ejecuta el procesamiento estadístico"""
        last_timeout_check = datetime.now()
        timeout_check_interval = 30  # Verificar timeouts cada 30 segundos
        
        while self.running:
            try:
                if not self.check_connection():
                    time.sleep(5)
                    continue

                now = datetime.now()
                
                # Verificar timeouts periódicamente
                if (now - last_timeout_check).total_seconds() >= timeout_check_interval:
                    self.check_timeout_periods()
                    last_timeout_check = now

                # Procesar mediciones pendientes
                success = self._process_pending_measurements()
                if not success:
                    time.sleep(5)
                    continue

                time.sleep(1)

            except Exception as e:
                logger.error(f"Error en procesamiento estadístico: {str(e)}")
                time.sleep(5)

    def _process_pending_measurements(self):
        """Procesa mediciones pendientes y cierra períodos cuando corresponde"""
        success, measurements = self.execute_query("""
            SELECT 
                bb.id,
                bb.dmac,
                bb.temp,
                bb.fecha,
                m.idSistema,
                e.idEntrada,
                d.factor
            FROM bandeja_blue bb
            JOIN modbus m ON bb.dmac = m.dmac
            JOIN entradas e ON m.idModbus = e.idModbus
            JOIN dispositivos d ON e.idDispositivo = d.idDispositivo 
            WHERE bb.procesado_el IS NULL
            ORDER BY bb.fecha
            LIMIT 1000
        """)

        if not success or not measurements:
            return True

        # Procesar cada medición en orden cronológico
        for measurement in measurements:
            dmac = measurement['dmac']
            fecha = measurement['fecha']
            
            # Determinar a qué período pertenece esta medición
            periodo_medicion = fecha.replace(
                minute=(fecha.minute // self.process_interval) * self.process_interval,
                second=0,
                microsecond=0
            )

            # Si es una entrada nueva, inicializarla
            if dmac not in self.current_periods:
                self.current_periods[dmac] = {
                    'period_start': periodo_medicion,
                    'idEntrada': measurement['idEntrada'],
                    'measurements': [],
                    'factor': measurement['factor']
                }
                
            current_info = self.current_periods[dmac]
            current_period_end = current_info['period_start'] + timedelta(minutes=self.process_interval)

            # Si la medición pertenece al siguiente período, cerrar el actual
            if periodo_medicion >= current_period_end:
                if current_info['measurements']:
                    self._close_period(dmac, current_info)

                # Iniciar nuevo período
                self.current_periods[dmac] = {
                    'period_start': periodo_medicion,
                    'idEntrada': measurement['idEntrada'],
                    'measurements': [measurement],
                    'factor': measurement['factor']
                }
            else:
                # Agregar la medición al período actual
                current_info['measurements'].append(measurement)

        return True

    def check_timeout_periods(self):
        """Verifica y cierra períodos que han excedido el tiempo de espera"""
        now = datetime.now()
        timeout_minutes = int(os.getenv('PERIOD_TIMEOUT_MINUTES', 2))
        
        # Verificar todas las entradas activas
        success, active_inputs = self.execute_query("""
            SELECT DISTINCT m.dmac, e.idEntrada
            FROM modbus m
            JOIN entradas e ON m.idModbus = e.idModbus
            WHERE m.estado = 1
        """)
        
        if not success:
            return
            
        active_dmacs = {input['dmac']: input['idEntrada'] for input in active_inputs}
        
        for dmac, id_entrada in active_dmacs.items():
            current_period = self.current_periods.get(dmac)
            
            if current_period:
                period_end = current_period['period_start'] + timedelta(minutes=self.process_interval)
                timeout_threshold = period_end + timedelta(minutes=timeout_minutes)
                
                # Si excedió el timeout, cerrar con los datos que tenga
                if now >= timeout_threshold:
                    if current_period['measurements']:
                        self._close_period(dmac, current_period, forced=True)
                    else:
                        self._save_null_record(id_entrada, period_end)
                    
                    # Iniciar nuevo período
                    next_period_start = period_end
                    self.current_periods[dmac] = {
                        'period_start': next_period_start,
                        'idEntrada': id_entrada,
                        'measurements': [],
                        'factor': current_period['factor']
                    }
            else:
                # Entrada activa sin período actual, inicializar
                period_start = now.replace(
                    minute=(now.minute // self.process_interval) * self.process_interval,
                    second=0,
                    microsecond=0
                )
                self.current_periods[dmac] = {
                    'period_start': period_start,
                    'idEntrada': id_entrada,
                    'measurements': [],
                    'factor': 1  # Factor por defecto
                }

    def _save_null_record(self, id_entrada, period_end):
        """Guarda un registro con valores NULL para una entrada sin datos"""
        success = self._save_statistics(
            id_entrada,
            period_end - timedelta(minutes=self.process_interval),
            {
                'promedio': None,
                'maximo': None,
                'minimo': None,
                'desvio': None
            }
        )
        
        if success and self.debug:
            logger.debug(
                f"Registro NULL generado - Entrada: {id_entrada}, "
                f"Período: {period_end:%Y-%m-%d %H:%M}"
            )

    def _close_period(self, dmac, period_info, forced=False):
        """Cierra un período, ya sea por nueva medición o por timeout"""
        try:
            measurements_to_process = period_info['measurements']
            factor = period_info['factor']
            
            # Ajustar temperaturas y redondear
            temps = [round(m['temp'] / factor, 1) for m in measurements_to_process]
            
            if temps:
                promedio = round(sum(temps) / len(temps), 1)
                maximo = round(max(temps), 1)
                minimo = round(min(temps), 1)
                
                if len(temps) > 1:
                    suma_cuadrados = sum((x - promedio) ** 2 for x in temps)
                    desvio = round((suma_cuadrados / (len(temps) - 1)) ** 0.5, 2)
                else:
                    desvio = 0.0

                # Guardar estadísticas
                success = self._save_statistics(
                    period_info['idEntrada'],
                    period_info['period_start'],
                    {
                        'promedio': promedio,
                        'maximo': maximo,
                        'minimo': minimo,
                        'desvio': desvio
                    }
                )

                if success:
                    # Marcar como procesadas
                    ids = [m['id'] for m in measurements_to_process]
                    self._mark_as_processed_batch(ids)
                    
                    if self.debug:
                        logger.debug(
                            f"Período cerrado {'por timeout' if forced else ''} - "
                            f"DMAC: {dmac}, "
                            f"Fin: {(period_info['period_start'] + timedelta(minutes=self.process_interval)):%Y-%m-%d %H:%M}, "
                            f"Mediciones: {len(temps)}"
                        )
                        
            return True
        except Exception as e:
            logger.error(f"Error cerrando período para {dmac}: {str(e)}")
            return False

    def _save_statistics(self, id_entrada, period_start, stats):
        """Guarda las estadísticas calculadas"""
        try:
            # Asegurar que el fin del período esté alineado con los cuartos de hora
            period_end = period_start + timedelta(minutes=self.process_interval)
            period_end = period_end.replace(
                minute=(period_end.minute // self.process_interval) * self.process_interval,
                second=0,
                microsecond=0
            )

            if self.debug:
                logger.debug(
                    f"Guardando estadísticas - Entrada: {id_entrada}, "
                    f"Período: {period_start:%Y-%m-%d %H:%M} -> {period_end:%Y-%m-%d %H:%M}"
                )

            # Ejecutar el insert/update
            success, result = self.execute_query("""
                INSERT INTO registros 
                    (idSistema, idEntrada, fecha, medio, maximo, minimo, desvio)
                VALUES 
                    (%s, %s, %s, %s, %s, %s, %s)
                ON DUPLICATE KEY UPDATE
                    medio = VALUES(medio),
                    maximo = VALUES(maximo),
                    minimo = VALUES(minimo),
                    desvio = VALUES(desvio)
            """, (
                0,  # idSistema
                id_entrada,
                period_end,  # Usar el fin del período como timestamp
                stats['promedio'],
                stats['maximo'],
                stats['minimo'],
                stats.get('desvio', 0)
            ), commit=True)

            return success

        except Exception as e:
            logger.error(f"Error guardando estadísticas: {str(e)}")
            return False

    def _mark_as_processed_batch(self, ids):
        """Marca un lote de mediciones como procesadas"""
        if not ids:
            return

        try:
            placeholders = ', '.join(['%s'] * len(ids))
            success, result = self.execute_query(
                f"""
                UPDATE bandeja_blue 
                SET procesado_el = %s 
                WHERE id IN ({placeholders})
                AND procesado_el IS NULL
                """,
                [datetime.now()] + ids,
                commit=True
            )

            if success and self.debug:
                logger.debug(f"Marcadas {len(ids)} mediciones como procesadas")

        except Exception as e:
            logger.error(f"Error en mark_as_processed_batch: {str(e)}")

    def _load_configurations(self):
        """Carga las configuraciones desde la base de datos"""
        try:
            success, result = self.execute_query("""
                SELECT 
                    d.idDispositivo,
                    d.factor,
                    c.idSistema,
                    c.idEntrada,
                    c.vMin1,
                    c.vMax1,
                    c.tMax1,
                    c.generaAlarma
                FROM configuraciones c
                JOIN entradas e ON c.idEntrada = e.idEntrada AND c.idSistema = e.idSistema
                JOIN dispositivos d ON e.idDispositivo = d.idDispositivo
                WHERE c.generaAlarma = 1
            """)

            if success:
                for config in result:
                    key = (config['idSistema'], config['idEntrada'])
                    self.configs[key] = {
                        'idDispositivo': config['idDispositivo'],
                        'factor': config['factor'],
                        'vMin1': config['vMin1'],
                        'vMax1': config['vMax1'],
                        'tMax1': config['tMax1'],
                        'generaAlarma': config['generaAlarma']
                    }
                logger.info(f"Configuraciones cargadas: {len(self.configs)} entradas")
            else:
                logger.error("Error cargando configuraciones")

        except Exception as e:
            logger.error(f"Error en carga de configuraciones: {str(e)}")

    def get_metrics(self):
        """Obtiene métricas del procesador"""
        return {
            'processed_measurements': self.metrics['processed_measurements'],
            'generated_records': self.metrics['generated_records'],
            'active_periods': self.metrics['active_periods'],
            'current_periods': [
                {
                    'dmac': dmac,
                    'idEntrada': info['idEntrada'],
                    'period_start': info['period_start'].isoformat()
                }
                for dmac, info in self.current_periods.items()
            ]
        }
        
    def process_historical_data(self):
        """Procesa datos históricos pendientes"""
        if not self.check_connection():
            return False

        logger.info("Iniciando procesamiento histórico...")
        total_records_generated = 0
        total_measurements_processed = 0

        try:
            # Obtener el último cuarto de hora completo
            now = datetime.now()
            ultimo_cuarto = now.replace(
                minute=(now.minute // PROCESS_INTERVAL) * PROCESS_INTERVAL,
                second=0,
                microsecond=0
            )

            # Obtener el rango de fechas a procesar
            success, result = self.execute_query("""
                SELECT MIN(fecha) as inicio, MAX(fecha) as fin
                FROM bandeja_blue
                WHERE procesado_el IS NULL
                AND fecha < %s
            """, (ultimo_cuarto,))

            if not success or not result or not result[0]['inicio']:
                logger.info("No hay datos históricos pendientes")
                return True

            fecha_inicio = result[0]['inicio']
            fecha_actual = fecha_inicio.replace(
                minute=(fecha_inicio.minute // PROCESS_INTERVAL) * PROCESS_INTERVAL,
                second=0,
                microsecond=0
            )

            # Procesar por períodos hasta el último cuarto de hora
            while fecha_actual < ultimo_cuarto:
                fecha_siguiente = fecha_actual + timedelta(minutes=PROCESS_INTERVAL)
                
                # Obtener mediciones del período
                success, measurements = self.execute_query("""
                    SELECT 
                        bb.id,
                        bb.dmac,
                        bb.temp,
                        bb.fecha,
                        m.idSistema,
                        e.idEntrada,
                        d.factor
                    FROM bandeja_blue bb
                    JOIN modbus m ON bb.dmac = m.dmac
                    JOIN entradas e ON m.idModbus = e.idModbus
                    JOIN dispositivos d ON e.idDispositivo = d.idDispositivo 
                    WHERE bb.procesado_el IS NULL
                    AND bb.fecha >= %s
                    AND bb.fecha < %s
                    ORDER BY bb.fecha
                """, (fecha_actual, fecha_siguiente))

                if not success:
                    logger.error("Error obteniendo mediciones históricas")
                    return False

                if measurements:
                    # Agrupar por DMAC
                    period_groups = defaultdict(list)
                    for m in measurements:
                        period_groups[m['dmac']].append(m)

                    # Procesar cada grupo
                    for dmac, group in period_groups.items():
                        try:
                            factor = group[0]['factor']
                            # Ajustar temperaturas y redondear
                            temps = [round(m['temp'] / factor, 1) for m in group]
                            
                            if temps:
                                promedio = round(sum(temps) / len(temps), 1)
                                maximo = round(max(temps), 1)
                                minimo = round(min(temps), 1)
                                
                                if len(temps) > 1:
                                    suma_cuadrados = sum((x - promedio) ** 2 for x in temps)
                                    desvio = round((suma_cuadrados / (len(temps) - 1)) ** 0.5, 2)
                                else:
                                    desvio = 0.0

                                # Guardar estadísticas
                                if self._save_statistics(
                                    group[0]['idEntrada'],
                                    fecha_actual,
                                    {
                                        'promedio': promedio,
                                        'maximo': maximo,
                                        'minimo': minimo,
                                        'desvio': desvio
                                    }
                                ):
                                    total_records_generated += 1
                                    ids = [m['id'] for m in group]
                                    self._mark_as_processed_batch(ids)
                                    total_measurements_processed += len(group)
                                    
                                    if self.verbose:
                                        logger.info(
                                            f"Período {fecha_actual:%Y-%m-%d %H:%M} - "
                                            f"DMAC: {dmac} procesado - "
                                            f"Mediciones: {len(group)}"
                                        )
                        except Exception as e:
                            logger.error(f"Error procesando grupo histórico {dmac}: {str(e)}")
                            continue

                # Avanzar al siguiente período
                fecha_actual = fecha_siguiente

            logger.info(
                f"Procesamiento histórico completado. "
                f"Total registros generados: {total_records_generated}, "
                f"Total mediciones procesadas: {total_measurements_processed}"
            )
            return True

        except Exception as e:
            logger.error(f"Error en procesamiento histórico: {str(e)}")
            return False