#!/usr/bin/env python3
"""
Device Discovery - Refactored with Unified Architecture
=======================================================
Zentrale Datenbank-Logik im Hauptscript
Einheitliche Schnittstelle für alle Module
"""

import pymysql
from pymysql import Error
import json
import logging
import configparser
import os
from typing import Optional, List, Dict

# Import Module
from modules.tahoma_module import TahomaModule
from modules.wled_module import WLEDModule
from modules.mqtt_module import MQTTModule
from modules.shelly_module import ShellyModule

# Logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


# ============================================================================
# CONFIG CLASS (Original)
# ============================================================================

class Config:
    """Lädt und verwaltet die Konfiguration"""
    
    def __init__(self, config_file: str = 'config.ini'):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        config_path = os.path.join(script_dir, config_file)
        
        if not os.path.exists(config_path):
            raise FileNotFoundError(
                f"Konfigurationsdatei '{config_file}' nicht gefunden in {script_dir}"
            )
        
        self.config = configparser.ConfigParser()
        self.config.read(config_path, encoding='utf-8')
        logger.info(f"Konfiguration geladen von: {config_path}")
        self._validate()
    
    def _validate(self):
        required_sections = ['database', 'options']
        for section in required_sections:
            if not self.config.has_section(section):
                raise ValueError(f"Erforderliche Sektion '[{section}]' fehlt")
    
    def _get_bool(self, section: str, key: str, fallback: bool = False) -> bool:
        value = self.config.get(section, key, fallback=str(fallback)).strip().lower()
        return value in ('true', '1', 'yes', 'on')
    
    def _get_int(self, section: str, key: str, fallback: int = 0) -> int:
        try:
            return self.config.getint(section, key, fallback=fallback)
        except ValueError:
            return fallback
    
    def _get_list(self, section: str, key: str) -> List[str]:
        value = self.config.get(section, key, fallback='').strip()
        if not value:
            return []
        return [item.strip() for item in value.split(',') if item.strip()]
    
    # Database
    @property
    def db_host(self) -> str:
        return self.config.get('database', 'host').strip()
    
    @property
    def db_port(self) -> int:
        return self._get_int('database', 'port', 3306)
    
    @property
    def db_name(self) -> str:
        return self.config.get('database', 'database').strip()
    
    @property
    def db_user(self) -> str:
        return self.config.get('database', 'user').strip()
    
    @property
    def db_password(self) -> str:
        return self.config.get('database', 'password').strip()
    
    # Tahoma
    @property
    def tahoma_enable(self) -> bool:
        if not self.config.has_section('tahoma'):
            return False
        return self._get_bool('tahoma', 'enable', False)
    
    @property
    def tahoma_ip(self) -> str:
        if not self.config.has_section('tahoma'):
            return ''
        return self.config.get('tahoma', 'ip', fallback='').strip()
    
    @property
    def tahoma_token(self) -> str:
        if not self.config.has_section('tahoma'):
            return ''
        return self.config.get('tahoma', 'token', fallback='').strip()
    
    @property
    def tahoma_timeout(self) -> int:
        if not self.config.has_section('tahoma'):
            return 10
        return self._get_int('tahoma', 'timeout', 10)
    
    # WLED
    @property
    def wled_enable(self) -> bool:
        if not self.config.has_section('wled'):
            return False
        return self._get_bool('wled', 'enable', False)
    
    @property
    def wled_discovery_timeout(self) -> int:
        if not self.config.has_section('wled'):
            return 5
        return self._get_int('wled', 'discovery_timeout', 5)
    
    @property
    def wled_manual_ips(self) -> List[str]:
        if not self.config.has_section('wled'):
            return []
        return self._get_list('wled', 'manual_ips')
    
    @property
    def wled_timeout(self) -> int:
        if not self.config.has_section('wled'):
            return 2
        return self._get_int('wled', 'timeout', 2)
    
    # MQTT
    @property
    def mqtt_enable(self) -> bool:
        if not self.config.has_section('mqtt'):
            return False
        return self._get_bool('mqtt', 'enable', False)
    
    @property
    def mqtt_broker(self) -> str:
        if not self.config.has_section('mqtt'):
            return 'localhost'
        return self.config.get('mqtt', 'broker', fallback='localhost').strip()
    
    @property
    def mqtt_port(self) -> int:
        if not self.config.has_section('mqtt'):
            return 1883
        return self._get_int('mqtt', 'port', 1883)
    
    @property
    def mqtt_username(self) -> Optional[str]:
        if not self.config.has_section('mqtt'):
            return None
        value = self.config.get('mqtt', 'username', fallback='').strip()
        return value if value else None
    
    @property
    def mqtt_password(self) -> Optional[str]:
        if not self.config.has_section('mqtt'):
            return None
        value = self.config.get('mqtt', 'password', fallback='').strip()
        return value if value else None
    
    @property
    def mqtt_discovery_prefix(self) -> str:
        if not self.config.has_section('mqtt'):
            return 'homeassistant'
        return self.config.get('mqtt', 'discovery_prefix', fallback='homeassistant').strip()
    
    @property
    def mqtt_discovery_timeout(self) -> int:
        if not self.config.has_section('mqtt'):
            return 10
        return self._get_int('mqtt', 'discovery_timeout', 10)
    
    # Shelly
    @property
    def shelly_enable(self) -> bool:
        if not self.config.has_section('shelly'):
            return False
        return self._get_bool('shelly', 'enable', False)
    
    @property
    def shelly_network_range(self) -> str:
        if not self.config.has_section('shelly'):
            return '192.168.1'
        return self.config.get('shelly', 'network_range', fallback='192.168.1').strip()
    
    @property
    def shelly_start_ip(self) -> int:
        if not self.config.has_section('shelly'):
            return 1
        return self._get_int('shelly', 'start_ip', 1)
    
    @property
    def shelly_end_ip(self) -> int:
        if not self.config.has_section('shelly'):
            return 254
        return self._get_int('shelly', 'end_ip', 254)
    
    # Options
    @property
    def clear_tables(self) -> bool:
        return self._get_bool('options', 'clear_tables', True)
    
    @property
    def log_level(self) -> str:
        level = self.config.get('options', 'log_level', fallback='INFO').strip().upper()
        valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
        return level if level in valid_levels else 'INFO'
    
    @property
    def log_file(self) -> Optional[str]:
        value = self.config.get('options', 'log_file', fallback='').strip()
        return value if value else None


# ============================================================================
# DATABASE MANAGER (Zentral im Hauptscript)
# ============================================================================

class DatabaseManager:
    """Zentrale Datenbank-Verwaltung - ALLE DB-Operationen hier"""
    
    def __init__(self, host: str, database: str, user: str, password: str, port: int = 3306):
        self.host = host
        self.database = database
        self.user = user
        self.password = password
        self.port = port
        self.connection = None
    
    def connect(self) -> bool:
        """Stellt Verbindung zur Datenbank her"""
        try:
            self.connection = pymysql.connect(
                host=self.host,
                database=self.database,
                user=self.user,
                password=self.password,
                port=self.port,
                charset='utf8mb4'
            )
            logger.info("✓ Erfolgreich mit MySQL-Datenbank verbunden")
            return True
        except Error as e:
            logger.error(f"✗ Datenbankverbindung fehlgeschlagen: {e}")
            return False
    
    def disconnect(self):
        """Schließt Datenbankverbindung"""
        if self.connection:
            self.connection.close()
            logger.info("Datenbankverbindung geschlossen")
    
    def clear_tables(self):
        """Löscht alle Einträge aus allen Tabellen"""
        try:
            cursor = self.connection.cursor()
            cursor.execute("SET FOREIGN_KEY_CHECKS=0")
            
            cursor.execute("DELETE FROM command_parameters")
            cursor.execute("DELETE FROM actor_commands")
            cursor.execute("DELETE FROM actor_states")
            cursor.execute("DELETE FROM actors")
            cursor.execute("DELETE FROM sensor_states")
            cursor.execute("DELETE FROM sensors")
            
            cursor.execute("SET FOREIGN_KEY_CHECKS=1")
            
            self.connection.commit()
            logger.info("✓ Alle Tabellen geleert")
            cursor.close()
        except Error as e:
            logger.error(f"✗ Fehler beim Leeren der Tabellen: {e}")
            self.connection.rollback()
    
    def insert_actor(self, device_type: str, name: str, url: str, 
                     commands: list, states: list) -> bool:
        """
        Fügt einen Aktor in die Datenbank ein
        
        Args:
            device_type: Typ des Geräts
            name: Name des Geräts
            url: Eindeutige URL/ID
            commands: Liste von Command-Dicts
            states: Liste von State-Dicts
        """
        try:
            cursor = self.connection.cursor()
            
            # Actor einfügen
            query = """
                INSERT INTO actors (type, name, parameters, url)
                VALUES (%s, %s, NULL, %s)
            """
            cursor.execute(query, (device_type, name, url))
            actor_id = cursor.lastrowid
            
            # Commands einfügen
            for cmd in commands:
                command_name = cmd.get('command', '')
                
                cmd_query = """
                    INSERT INTO actor_commands (actor_id, command_name)
                    VALUES (%s, %s)
                """
                cursor.execute(cmd_query, (actor_id, command_name))
                command_id = cursor.lastrowid
                
                # Parameter einfügen
                for param in cmd.get('parameters', []):
                    param_query = """
                        INSERT INTO command_parameters 
                        (command_id, parameter_name, parameter_type, min_value, max_value, possible_values, url)
                        VALUES (%s, %s, %s, %s, %s, %s, %s)
                    """
                    param_name = param.get('name', '')
                    param_type = param.get('type', '')
                    min_val = param.get('min')
                    max_val = param.get('max')
                    possible_vals = json.dumps(param.get('values')) if 'values' in param else None
                    param_url = param.get('url')
                    
                    cursor.execute(param_query, 
                                 (command_id, param_name, param_type, min_val, max_val, possible_vals, param_url))
            
            # States einfügen
            for state in states:
                state_query = """
                    INSERT INTO actor_states 
                    (actor_id, state_name, state_type, current_value, unit, url)
                    VALUES (%s, %s, %s, %s, %s, %s)
                """
                state_name = state.get('name', '')
                state_type = state.get('type', 0)
                current_value = str(state.get('current_value', '')) if 'current_value' in state else None
                unit = state.get('unit')
                state_url = state.get('url')
                
                cursor.execute(state_query, (actor_id, state_name, state_type, current_value, unit, state_url))
            
            self.connection.commit()
            cursor.close()
            return True
            
        except Error as e:
            logger.error(f"✗ Fehler beim Einfügen des Aktors {name}: {e}")
            self.connection.rollback()
            return False
    
    def insert_sensor(self, device_type: str, name: str, url: str, states: list) -> bool:
        """
        Fügt einen Sensor in die Datenbank ein
        
        Args:
            device_type: Typ des Sensors
            name: Name des Sensors
            url: Eindeutige URL/ID
            states: Liste von State-Dicts
        """
        try:
            cursor = self.connection.cursor()
            
            # Sensor einfügen
            query = """
                INSERT INTO sensors (type, name, parameters, url)
                VALUES (%s, %s, NULL, %s)
            """
            cursor.execute(query, (device_type, name, url))
            sensor_id = cursor.lastrowid
            
            # States einfügen
            for state in states:
                state_query = """
                    INSERT INTO sensor_states 
                    (sensor_id, state_name, state_type, current_value, unit, url)
                    VALUES (%s, %s, %s, %s, %s, %s)
                """
                state_name = state.get('name', '')
                state_type = state.get('type', 0)
                current_value = str(state.get('current_value', '')) if 'current_value' in state else None
                unit = state.get('unit')
                state_url = state.get('url')
                
                cursor.execute(state_query, (sensor_id, state_name, state_type, current_value, unit, state_url))
            
            self.connection.commit()
            cursor.close()
            return True
            
        except Error as e:
            logger.error(f"✗ Fehler beim Einfügen des Sensors {name}: {e}")
            self.connection.rollback()
            return False


# ============================================================================
# MAIN FUNCTION
# ============================================================================

def main():
    """Hauptfunktion - orchestriert alle Module mit einheitlicher Schnittstelle"""
    
    # Konfiguration laden
    try:
        config = Config('config.ini')
        logger.info("✓ Konfiguration erfolgreich geladen")
    except FileNotFoundError as e:
        print(f"FEHLER: {e}")
        return
    except Exception as e:
        print(f"FEHLER: {e}")
        return
    
    # Logging anpassen
    log_level = getattr(logging, config.log_level)
    logger.setLevel(log_level)
    
    if config.log_file:
        file_handler = logging.FileHandler(config.log_file, encoding='utf-8')
        file_handler.setLevel(log_level)
        file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
        logger.addHandler(file_handler)
        logger.info(f"Logging in Datei: {config.log_file}")
    
    # Datenbank initialisieren
    logger.info("Verbinde mit MySQL-Datenbank...")
    db = DatabaseManager(
        config.db_host,
        config.db_name,
        config.db_user,
        config.db_password,
        config.db_port
    )
    
    if not db.connect():
        logger.error("Datenbankverbindung fehlgeschlagen. Abbruch.")
        return
    
    try:
        # Tabellen leeren
        if config.clear_tables:
            db.clear_tables()
        
        total_actors = 0
        total_sensors = 0
        
        # Module initialisieren
        modules = [
            TahomaModule(config),
            WLEDModule(config),
            MQTTModule(config),
            ShellyModule(config)
        ]
        
        # Jedes Modul durchlaufen
        for module in modules:
            if not module.is_enabled():
                logger.info(f"Modul {module.get_name()} ist deaktiviert")
                continue
            
            try:
                # Discovery durchführen (einheitliche Schnittstelle!)
                actors, sensors = module.discover()
                
                # Actors in DB speichern
                for actor in actors:
                    if db.insert_actor(
                        actor['type'],
                        actor['name'],
                        actor['url'],
                        actor.get('commands', []),
                        actor.get('states', [])
                    ):
                        total_actors += 1
                        logger.info(f"  ✓ Actor: {actor['name']} ({actor['type']})")
                
                # Sensors in DB speichern
                for sensor in sensors:
                    if db.insert_sensor(
                        sensor['type'],
                        sensor['name'],
                        sensor['url'],
                        sensor.get('states', [])
                    ):
                        total_sensors += 1
                        logger.info(f"  ✓ Sensor: {sensor['name']} ({sensor['type']})")
            
            except Exception as e:
                logger.error(f"✗ Fehler bei Modul {module.get_name()}: {e}")
                import traceback
                traceback.print_exc()
        
        # Zusammenfassung
        logger.info("\n" + "=" * 60)
        logger.info("ZUSAMMENFASSUNG")
        logger.info("=" * 60)
        logger.info(f"Aktoren gespeichert:   {total_actors}")
        logger.info(f"Sensoren gespeichert:  {total_sensors}")
        logger.info("=" * 60)
        
        logger.info("\n✓ Import erfolgreich abgeschlossen!")
        
    except Exception as e:
        logger.error(f"✗ Fehler: {e}")
        import traceback
        traceback.print_exc()
    
    finally:
        db.disconnect()


if __name__ == "__main__":
    main()
