"""
Session Manager für die Lizenz-Session-Verwaltung mit Heartbeat.
"""

import threading
import time
import logging
import json
import os
import requests
from datetime import datetime
from typing import Optional, Dict, Any
from .api_client import LicenseAPIClient
from .hardware_fingerprint import HardwareFingerprint

logger = logging.getLogger("session_manager")
logger.setLevel(logging.DEBUG)
# Füge Console Handler hinzu falls noch nicht vorhanden
if not logger.handlers:
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
    logger.addHandler(handler)


class SessionManager:
    """Verwaltet die Lizenz-Session und Heartbeat."""
    
    SESSION_FILE = os.path.join("config", ".session_data")
    HEARTBEAT_INTERVAL = 60  # Sekunden
    
    def __init__(self, api_client: Optional[LicenseAPIClient] = None):
        """
        Initialisiert den Session Manager.
        
        Args:
            api_client: Optional vorkonfigurierter API Client
        """
        self.api_client = api_client or LicenseAPIClient()
        self.hardware_fingerprint = HardwareFingerprint()
        
        self.session_token: Optional[str] = None
        self.license_key: Optional[str] = None
        self.activation_id: Optional[int] = None
        self.heartbeat_thread: Optional[threading.Thread] = None
        self.stop_heartbeat = threading.Event()
        self.is_active = False
        
        # Lade Session-IP-Konfiguration
        self._load_ip_config()
        
        # Session-Daten laden falls vorhanden
        self._load_session_data()
    
    def _save_session_data(self) -> None:
        """Speichert die aktuelle Session-Daten."""
        try:
            os.makedirs("config", exist_ok=True)
            session_data = {
                "session_token": self.session_token,
                "license_key": self.license_key,
                "activation_id": self.activation_id,
                "timestamp": datetime.now().isoformat()
            }
            with open(self.SESSION_FILE, 'w') as f:
                json.dump(session_data, f)
            logger.debug("Session-Daten gespeichert")
        except Exception as e:
            logger.error(f"Fehler beim Speichern der Session-Daten: {e}")
    
    def _load_session_data(self) -> None:
        """Lädt gespeicherte Session-Daten."""
        if os.path.exists(self.SESSION_FILE):
            try:
                with open(self.SESSION_FILE, 'r') as f:
                    data = json.load(f)
                    self.session_token = data.get("session_token")
                    self.license_key = data.get("license_key")
                    self.activation_id = data.get("activation_id")
                    logger.info("Session-Daten geladen")
            except Exception as e:
                logger.warning(f"Fehler beim Laden der Session-Daten: {e}")
    
    def _clear_session_data(self) -> None:
        """Löscht die gespeicherten Session-Daten."""
        try:
            if os.path.exists(self.SESSION_FILE):
                os.remove(self.SESSION_FILE)
                logger.debug("Session-Daten gelöscht")
        except Exception as e:
            logger.error(f"Fehler beim Löschen der Session-Daten: {e}")
    
    def start_session(self, license_key: str, activation_id: Optional[int] = None) -> Dict[str, Any]:
        """
        Startet eine neue Session für die Lizenz.
        
        Args:
            license_key: Der Lizenzschlüssel
            activation_id: Optional die Aktivierungs-ID
            
        Returns:
            Dictionary mit Session-Informationen oder Fehler
        """
        if self.is_active:
            logger.warning("Session läuft bereits")
            return {
                "success": False,
                "error": "Session already active"
            }
        
        # Hardware-Info sammeln
        hw_hash = self.hardware_fingerprint.get_or_create_fingerprint()
        machine_name = self.hardware_fingerprint.get_machine_name()
        
        # IP-Adresse ermitteln
        client_ip = self._get_session_ip()
        
        logger.info(f"Starte Session für Lizenz: {license_key[:4]}...")
        logger.debug(f"Session-Parameter: machine_name={machine_name}, hw_hash={hw_hash[:8]}..., ip={client_ip}")
        
        # Session-Start API Call mit IP-Adresse
        result = self.api_client.start_session(
            license_key=license_key,
            machine_id=machine_name,
            hardware_hash=hw_hash,
            version="1.0.0",  # TODO: Version aus config lesen
            ip_address=client_ip  # NEU: IP-Adresse hinzugefügt
        )
        
        logger.debug(f"Session-Start Response: {result}")
        
        if result.get("success"):
            data = result.get("data", {})
            
            # Prüfe ob die Session wirklich erfolgreich war
            if data.get("success") is False:
                # Session wurde abgelehnt
                error_msg = data.get("message", "Session start failed")
                logger.error(f"Session abgelehnt: {error_msg}")
                return {
                    "success": False,
                    "error": error_msg,
                    "code": "SESSION_REJECTED"
                }
            
            self.session_token = data.get("session_token")
            self.license_key = license_key
            self.activation_id = activation_id or data.get("activation_id")
            self.is_active = True if self.session_token else False
            
            # Session-Daten speichern
            self._save_session_data()
            
            # Heartbeat starten
            self._start_heartbeat()
            
            logger.info(f"Session erfolgreich gestartet: {self.session_token}")
            
            # Update-Info prüfen
            if data.get("update_available"):
                logger.info(f"Update verfügbar: {data.get('latest_version')}")
            
            return {
                "success": True,
                "session_token": self.session_token,
                "update_info": {
                    "available": data.get("update_available", False),
                    "version": data.get("latest_version"),
                    "download_url": data.get("download_url")
                }
            }
        else:
            error = result.get("error", "Unknown error")
            logger.error(f"Session-Start fehlgeschlagen: {error}")
            
            # Bei Konflikt (409) bedeutet es, dass bereits eine Session läuft
            if result.get("status") == 409:
                return {
                    "success": False,
                    "error": "Another session is already active for this license",
                    "code": "SESSION_CONFLICT"
                }
            
            return {
                "success": False,
                "error": error,
                "code": result.get("code", "SESSION_START_FAILED")
            }
    
    def _start_heartbeat(self) -> None:
        """Startet den Heartbeat-Thread."""
        if self.heartbeat_thread and self.heartbeat_thread.is_alive():
            logger.warning("Heartbeat läuft bereits")
            return
        
        self.stop_heartbeat.clear()
        self.heartbeat_thread = threading.Thread(
            target=self._heartbeat_worker,
            daemon=True,
            name="LicenseHeartbeat"
        )
        self.heartbeat_thread.start()
        logger.info("Heartbeat-Thread gestartet")
    
    def _heartbeat_worker(self) -> None:
        """Worker-Funktion für den Heartbeat-Thread."""
        logger.info(f"Heartbeat-Worker gestartet (Interval: {self.HEARTBEAT_INTERVAL}s)")
        
        while not self.stop_heartbeat.is_set():
            try:
                # Warte das Interval oder bis Stop-Signal
                if self.stop_heartbeat.wait(self.HEARTBEAT_INTERVAL):
                    break
                
                # Sende Heartbeat
                if self.session_token and self.license_key:
                    logger.debug("Sende Heartbeat...")
                    result = self.api_client.session_heartbeat(
                        session_token=self.session_token,
                        license_key=self.license_key
                    )

                    # Pruefe sowohl HTTP-Status als auch Body-Success
                    http_ok = result.get("success")
                    body_data = result.get("data", {})
                    body_ok = body_data.get("success", True) if isinstance(body_data, dict) else True

                    if http_ok and body_ok:
                        logger.debug("Heartbeat erfolgreich")
                    else:
                        body_msg = body_data.get("message", "") if isinstance(body_data, dict) else ""
                        logger.error(f"Heartbeat fehlgeschlagen: {body_msg or result.get('error')}")

                        # Bei HTTP-Fehlern oder Body-Fehler Session beenden
                        if result.get("status") in [401, 404] or (http_ok and not body_ok):
                            logger.error("Session ungueltig, beende...")
                            self.end_session()
                            break
                else:
                    logger.warning("Keine Session-Daten für Heartbeat")
                    
            except Exception as e:
                logger.error(f"Fehler im Heartbeat-Worker: {e}")
        
        logger.info("Heartbeat-Worker beendet")
    
    def end_session(self) -> Dict[str, Any]:
        """
        Beendet die aktuelle Session.
        
        Returns:
            Dictionary mit Informationen über die beendete Session
        """
        if not self.is_active:
            logger.warning("Keine aktive Session zum Beenden")
            return {
                "success": False,
                "error": "No active session"
            }
        
        logger.info("Beende Session...")
        
        # Heartbeat stoppen
        self.stop_heartbeat.set()
        if self.heartbeat_thread:
            self.heartbeat_thread.join(timeout=5)
        
        # Session beenden API Call
        result = {"success": True}
        if self.session_token:
            result = self.api_client.end_session(self.session_token)
            
            if result.get("success"):
                logger.info("Session erfolgreich beendet")
            else:
                logger.error(f"Fehler beim Beenden der Session: {result.get('error')}")
        
        # Session-Daten löschen
        self.session_token = None
        self.license_key = None
        self.activation_id = None
        self.is_active = False
        self._clear_session_data()
        
        return result
    
    def resume_session(self) -> bool:
        """
        Versucht eine gespeicherte Session fortzusetzen.
        
        Returns:
            True wenn erfolgreich, False sonst
        """
        if self.is_active:
            logger.info("Session läuft bereits")
            return True
        
        if not self.session_token or not self.license_key:
            logger.info("Keine gespeicherten Session-Daten vorhanden")
            return False
        
        logger.info("Versuche Session fortzusetzen...")
        
        # Teste mit Heartbeat ob Session noch gültig ist
        result = self.api_client.session_heartbeat(
            session_token=self.session_token,
            license_key=self.license_key
        )

        # Pruefe sowohl HTTP-Status als auch Body-Success
        http_ok = result.get("success")
        body_data = result.get("data", {})
        body_ok = body_data.get("success", True) if isinstance(body_data, dict) else True

        if http_ok and body_ok:
            logger.info("Session erfolgreich fortgesetzt")
            self.is_active = True
            self._start_heartbeat()
            return True
        else:
            body_msg = body_data.get("message", "") if isinstance(body_data, dict) else ""
            logger.warning(f"Gespeicherte Session ungueltig: {body_msg or result.get('error', 'unbekannt')}")
            self._clear_session_data()
            return False
    
    def is_session_active(self) -> bool:
        """
        Prüft ob eine Session aktiv ist.
        
        Returns:
            True wenn aktiv, False sonst
        """
        return self.is_active
    
    def get_session_info(self) -> Dict[str, Any]:
        """
        Gibt Informationen über die aktuelle Session zurück.
        
        Returns:
            Dictionary mit Session-Informationen
        """
        return {
            "active": self.is_active,
            "session_token": self.session_token[:8] + "..." if self.session_token else None,
            "license_key": self.license_key[:4] + "..." if self.license_key else None,
            "activation_id": self.activation_id,
            "heartbeat_interval": self.HEARTBEAT_INTERVAL
        }
    
    def set_heartbeat_interval(self, seconds: int) -> None:
        """
        Setzt das Heartbeat-Interval.
        
        Args:
            seconds: Interval in Sekunden (min 30, max 300)
        """
        if 30 <= seconds <= 300:
            self.HEARTBEAT_INTERVAL = seconds
            logger.info(f"Heartbeat-Interval auf {seconds}s gesetzt")
            
            # Restart Heartbeat wenn aktiv
            if self.is_active:
                self.stop_heartbeat.set()
                if self.heartbeat_thread:
                    self.heartbeat_thread.join(timeout=5)
                self._start_heartbeat()
        else:
            logger.warning(f"Ungültiges Heartbeat-Interval: {seconds}")
    
    def _load_ip_config(self) -> None:
        """Lädt die IP-Konfiguration aus license_config.json."""
        config_path = os.path.join("config", "license_config.json")
        self.session_ip_mode = "auto"  # Default
        self.ip_fallback = "0.0.0.0"
        
        try:
            if os.path.exists(config_path):
                with open(config_path, 'r') as f:
                    config = json.load(f)
                    self.session_ip_mode = config.get("session_ip_mode", "auto")
                    self.ip_fallback = config.get("ip_fallback", "0.0.0.0")
                    logger.debug(f"IP-Konfiguration geladen: mode={self.session_ip_mode}, fallback={self.ip_fallback}")
        except Exception as e:
            logger.warning(f"Fehler beim Laden der IP-Konfiguration: {e}")
    
    def _get_session_ip(self) -> str:
        """
        Ermittelt die IP-Adresse für die Session basierend auf der Konfiguration.
        
        TESTBETRIEB: Temporäre Lösung - wird durch Server-Ressourcenmanagement ersetzt
        
        Returns:
            Die IP-Adresse als String
        """
        if self.session_ip_mode == "auto":
            # TESTBETRIEB: Auto-Erkennung der öffentlichen IP
            logger.info("TESTBETRIEB: Ermittle öffentliche IP-Adresse automatisch")
            try:
                response = requests.get("https://api.ipify.org?format=json", timeout=5)
                if response.status_code == 200:
                    ip = response.json().get("ip")
                    logger.info(f"Öffentliche IP ermittelt: {ip}")
                    return ip
                else:
                    logger.warning(f"IP-Ermittlung fehlgeschlagen: Status {response.status_code}")
            except Exception as e:
                logger.error(f"Fehler bei IP-Ermittlung: {e}")
            
            # Fallback verwenden
            logger.warning(f"Verwende Fallback-IP: {self.ip_fallback}")
            return self.ip_fallback
            
        elif self.session_ip_mode == "server_assigned":
            # TODO: Implementierung für Server-zugewiesene IPs
            logger.info("Server-assigned IP mode noch nicht implementiert, verwende Fallback")
            return self.ip_fallback
            
        elif self.session_ip_mode == "proxy":
            # TODO: Proxy-IP verwenden wenn Proxy aktiv
            logger.info("Proxy IP mode noch nicht implementiert, verwende Fallback")
            return self.ip_fallback
            
        else:
            logger.warning(f"Unbekannter IP-Modus: {self.session_ip_mode}, verwende Fallback")
            return self.ip_fallback


# Test-Funktion
if __name__ == "__main__":
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    
    print("=== Session Manager Test ===\n")
    
    # Session Manager erstellen
    session_mgr = SessionManager()
    
    # Session-Info anzeigen
    print("Aktuelle Session-Info:")
    info = session_mgr.get_session_info()
    for key, value in info.items():
        print(f"  {key}: {value}")
    
    # Versuche gespeicherte Session fortzusetzen
    print("\nVersuche Session fortzusetzen...")
    if session_mgr.resume_session():
        print("  ✓ Session fortgesetzt")
    else:
        print("  ✗ Keine gültige Session gefunden")
    
    print("\n=== Test abgeschlossen ===")