"""
Procesador de desvíos - MVP v1.0

Analiza las mediciones en tiempo real para detectar desvíos 
y generar alarmas cuando los valores exceden los límites configurados.
Este procesador es independiente del sistema de promediación por períodos.
"""

from datetime import datetime, timedelta
import threading
import time
import logging
from typing import Dict, Tuple
from collections import defaultdict
from .base import BaseProcessor
from ..config.constants import (
    STATE_NORMAL,
    STATE_ABOVE_MAX,
    STATE_ALARM_MAX,
    STATE_BELOW_MIN,
    STATE_ALARM_MIN,
    DEVIATION_BATCH_SIZE,
    DEVIATION_CONFIG_UPDATE_INTERVAL
)

logger = logging.getLogger(__name__)

class DeviationProcessor(BaseProcessor):
    def __init__(self, db_name):
        super().__init__(db_name)
        self.deviation_states = defaultdict(self._create_default_state)
        self.configs = defaultdict(dict)
        self.batch_alarms = []
        self.alarm_lock = threading.Lock()
        self.batch_size = DEVIATION_BATCH_SIZE
        self.last_config_update = None
        self.config_update_interval = DEVIATION_CONFIG_UPDATE_INTERVAL
        logger.info("DeviationProcessor inicializado")
        self.load_configurations()

    def _create_default_state(self):
        """Factory para estados por defecto"""
        return {
            'state_id': STATE_NORMAL,
            'start_time': None,
            'last_value': None
        }

    def load_configurations(self):
        """Carga las configuraciones de umbrales con cache temporal"""
        now = datetime.now()
        if (self.last_config_update is None or 
            (now - self.last_config_update).total_seconds() > self.config_update_interval):
            
            try:
                if not self.check_connection():
                    logger.error("No se pudo establecer conexión con la base de datos")
                    return

                cursor = self.conn.cursor(dictionary=True)
                cursor.execute("""
                    SELECT idSistema, idEntrada, vMin1, vMax1, tMax1, generaAlarma 
                    FROM configuraciones
                    WHERE generaAlarma = 1
                """)
                
                new_configs = defaultdict(dict)
                for config in cursor.fetchall():
                    key = (config['idSistema'], config['idEntrada'])
                    new_configs[key] = config
                
                self.configs = new_configs
                self.last_config_update = now
                logger.info(f"Configuraciones actualizadas. Total entradas: {len(self.configs)}")
            
            except Exception as e:
                logger.error(f"Error cargando configuraciones: {str(e)}")
            finally:
                if 'cursor' in locals():
                    cursor.close()
  
    def _process_measurement(self, key, measurement):
        """Procesa una medición individual para detección de desvíos"""
        config = self.configs[key]
        valor = measurement['temp']
        current_time = measurement['fecha']  # Usar el timestamp original de la medición
        
        # Verificar si está fuera de rango
        if valor > config['vMax1']:
            self._handle_high_value(key, valor, config, current_time)
        elif valor < config['vMin1']:
            self._handle_low_value(key, valor, config, current_time)
        else:
            self._reset_state(key)
       
    def _handle_low_value(self, key, valor, config, current_time):
        """Maneja un valor por debajo del mínimo"""
        state = self.deviation_states[key]
        
        if state['state_id'] == STATE_NORMAL:
            state['state_id'] = STATE_BELOW_MIN
            state['start_time'] = current_time
            state['last_value'] = valor
            if self.debug:
                logger.debug(f"Entrada {key} inició valor bajo mínimo: {valor}")
        
        elif state['state_id'] == STATE_BELOW_MIN:
            if (current_time - state['start_time']).total_seconds() >= config['tMax1']:
                if self.debug:
                    logger.debug(f"Entrada {key} generando alarma por mínimo: {valor}")
                self._add_to_alarm_batch(key[0], key[1], valor, STATE_ALARM_MIN, current_time)
                state['state_id'] = STATE_ALARM_MIN

    def _add_to_alarm_batch(self, id_sistema, id_entrada, valor, id_estado):
        """Añade una alarma al lote para inserción batch"""
        with self.alarm_lock:
            logger.debug(
                f"Agregando alarma - Sistema: {id_sistema}, "
                f"Entrada: {id_entrada}, Valor: {valor}, "
                f"Estado: {id_estado}"              
            )
            self.batch_alarms.append({
                'idSistema': id_sistema,
                'idEntrada': id_entrada,
                'valor': valor,
                'idEstado': id_estado,
                'fecha': datetime.now(),
                'activa': 1
            })

    def _flush_alarms(self):
        """Inserta todas las alarmas acumuladas en una sola transacción"""
        if not self.batch_alarms:
            return

        try:
            if not self.check_connection():
                logger.error("No se pudo establecer conexión con la base de datos")
                return

            cursor = self.conn.cursor()
            query = """
                INSERT INTO alarmas (idSistema, idEntrada, valor, idEstado, fecha, activa)
                VALUES (%s, %s, %s, %s, %s, %s)
            """
            
            values = [(a['idSistema'], a['idEntrada'], a['valor'], 
                      a['idEstado'], a['fecha'], a['activa']) 
                     for a in self.batch_alarms]
            
            cursor.executemany(query, values)
            self.conn.commit()
            
            if self.debug:
                logger.debug(f"Insertadas {len(self.batch_alarms)} alarmas en batch")
            
            self.batch_alarms.clear()
        
        except Exception as e:
            logger.error(f"Error insertando alarmas en batch: {str(e)}")
        finally:
            if 'cursor' in locals():
                cursor.close()

    def _reset_state(self, key):
        """Resetea el estado a normal cuando el valor vuelve a la banda permitida"""
        current_state = self.deviation_states[key]['state_id']
        if current_state != STATE_NORMAL:
            logger.debug(
                f"Entrada {key[1]}: Reseteando estado {current_state} a NORMAL"
            )
            self.deviation_states[key] = self._create_default_state()


    def stop(self):
        """Detiene el procesador y realiza limpieza"""
        self._flush_alarms()  # Asegurar que todas las alarmas pendientes se guarden
        super().stop()  # Llamar al método stop del padre
        logger.info("Procesador de desvíos detenido correctamente")
        
    def get_metrics(self):
        """
        Retorna las métricas actuales del procesador de desvíos
        """
        current_metrics = {
            'states': {
                'normal': 0,
                'above_max': 0,
                'below_min': 0,
                'alarm_max': 0,
                'alarm_min': 0
            },
            'configurations': {
                'total': len(self.configs),
                'last_update': self.last_config_update.isoformat() if self.last_config_update else None
            },
            'alarms': {
                'pending': len(self.batch_alarms)
            }
        }

        # Contar estados actuales
        for state_info in self.deviation_states.values():
            state_id = state_info['state_id']
            if state_id == STATE_NORMAL:
                current_metrics['states']['normal'] += 1
            elif state_id == STATE_ABOVE_MAX:
                current_metrics['states']['above_max'] += 1
            elif state_id == STATE_BELOW_MIN:
                current_metrics['states']['below_min'] += 1
            elif state_id == STATE_ALARM_MAX:
                current_metrics['states']['alarm_max'] += 1
            elif state_id == STATE_ALARM_MIN:
                current_metrics['states']['alarm_min'] += 1

        return current_metrics
    
    def process_pending(self):
        """Procesa las mediciones pendientes en tiempo real"""
        while self.running:
            try:
                if not self.check_connection():
                    logger.error("No se pudo establecer conexión con la base de datos")
                    time.sleep(5)
                    continue

                cursor = self.conn.cursor(dictionary=True)
                
                # Recargar configuraciones si es necesario
                self.load_configurations()
                
                # Obtener mediciones no procesadas
                cursor.execute("""
                    SELECT 
                        bb.id,
                        bb.dmac,
                        bb.temp,
                        bb.fecha,
                        m.idSistema,
                        e.idEntrada 
                    FROM bandeja_blue bb
                    JOIN modbus m ON bb.dmac = m.dmac
                    JOIN entradas e ON m.idModbus = e.idModbus
                    WHERE bb.procesado_el IS NULL
                    ORDER BY bb.fecha
                    LIMIT %s
                """, (self.batch_size,))
                
                measurements = cursor.fetchall()
                if not measurements:
                    if self.debug:
                        logger.debug("No hay nuevas mediciones para procesar")
                    time.sleep(1)
                    continue

                # Procesar mediciones
                processed_ids = []
                for measurement in measurements:
                    key = (measurement['idSistema'], measurement['idEntrada'])
                    if key in self.configs:
                        if self.debug:
                            logger.debug(
                                f"Procesando medición - DMAC: {measurement['dmac']}, "
                                f"Valor: {measurement['temp']}°C"
                            )
                        
                        config = self.configs[key]
                        valor = measurement['temp']
                        fecha = measurement['fecha']

                        # Verificar si está fuera de rango
                        if valor > config['vMax1']:
                            if self.debug:
                                logger.debug(
                                    f"Valor alto detectado - DMAC: {measurement['dmac']}, "
                                    f"Valor: {valor}°C > Max: {config['vMax1']}°C"
                                )
                            self._handle_high_value(key, valor, config, fecha)
                        elif valor < config['vMin1']:
                            if self.debug:
                                logger.debug(
                                    f"Valor bajo detectado - DMAC: {measurement['dmac']}, "
                                    f"Valor: {valor}°C < Min: {config['vMin1']}°C"
                                )
                            self._handle_low_value(key, valor, config, fecha)
                        else:
                            if self.debug:
                                logger.debug(
                                    f"Valor normal - DMAC: {measurement['dmac']}, "
                                    f"Valor: {valor}°C"
                                )
                            self._reset_state(key)

                    processed_ids.append(measurement['id'])

                # Marcar como procesados sin afectar los timestamps originales
                if processed_ids:
                    if self.debug:
                        logger.debug(f"Marcando {len(processed_ids)} mediciones como procesadas")
                    
                    placeholders = ', '.join(['%s'] * len(processed_ids))
                    update_cursor = self.conn.cursor()
                    update_query = f"""
                        UPDATE bandeja_blue 
                        SET procesado_el = %s 
                        WHERE id IN ({placeholders})
                    """
                    update_cursor.execute(update_query, [datetime.now()] + processed_ids)
                    self.conn.commit()

            except Exception as e:
                logger.error(f"Error procesando mediciones: {str(e)}", exc_info=True)
                time.sleep(5)
            finally:
                if 'cursor' in locals():
                    cursor.close()
                if 'update_cursor' in locals():
                    update_cursor.close()

    def _handle_high_value(self, key, valor, config, fecha):
        """Maneja un valor que supera el máximo"""
        state = self.deviation_states[key]
        
        if self.debug:
            logger.debug(
                f"Manejando valor alto - Sistema: {key[0]}, "
                f"Entrada: {key[1]}, Valor: {valor}°C, "
                f"Estado actual: {state['state_id']}"
            )
        
        if state['state_id'] == STATE_NORMAL:
            state['state_id'] = STATE_ABOVE_MAX
            state['start_time'] = fecha
            state['last_value'] = valor
            if self.debug:
                logger.debug(f"Cambiando a estado ABOVE_MAX")
        
        elif state['state_id'] == STATE_ABOVE_MAX:
            time_in_state = (fecha - state['start_time']).total_seconds()
            if self.debug:
                logger.debug(
                    f"Tiempo en ABOVE_MAX: {time_in_state} segundos "
                    f"(máximo permitido: {config['tMax1']} segundos)"
                )
            
            if time_in_state >= config['tMax1']:
                if self.debug:
                    logger.debug("Generando alarma por máximo")
                self._add_to_alarm_batch(key[0], key[1], valor, STATE_ALARM_MAX)
                state['state_id'] = STATE_ALARM_MAX