#!/usr/bin/env python3
"""
SSL-Konfigurationsmodul für MYP Druckerverwaltung
Automatische Generierung von selbstsignierten SSL-Zertifikaten für localhost
Optimiert für Debian/Linux-Systeme ohne Windows-Abhängigkeiten
"""

import os
import ssl
import subprocess
import logging
from pathlib import Path
from datetime import datetime, timedelta

# Logger für SSL-Konfiguration
ssl_logger = logging.getLogger('ssl_config')

class SSLCertificateManager:
    """Verwaltet SSL-Zertifikate für die Anwendung"""
    
    def __init__(self, app_dir="/opt/myp"):
        self.app_dir = Path(app_dir)
        self.ssl_dir = self.app_dir / "certs" / "localhost"
        self.cert_file = self.ssl_dir / "localhost.crt"
        self.key_file = self.ssl_dir / "localhost.key"
        
    def ensure_ssl_directory(self):
        """Stellt sicher, dass das SSL-Verzeichnis existiert"""
        self.ssl_dir.mkdir(parents=True, exist_ok=True)
        ssl_logger.info(f"SSL-Verzeichnis erstellt: {self.ssl_dir}")
        
    def generate_ssl_certificate(self, force_regenerate=False):
        """
        Generiert ein selbstsigniertes SSL-Zertifikat für localhost
        
        Args:
            force_regenerate (bool): Erzwingt Neugenerierung auch wenn Zertifikat existiert
            
        Returns:
            bool: True wenn erfolgreich, False bei Fehler
        """
        try:
            # Prüfe ob Zertifikat bereits existiert und gültig ist
            if not force_regenerate and self.is_certificate_valid():
                ssl_logger.info("Gültiges SSL-Zertifikat bereits vorhanden")
                return True
                
            self.ensure_ssl_directory()
            
            ssl_logger.info("Generiere neues SSL-Zertifikat für localhost...")
            
            # OpenSSL-Konfiguration für erweiterte Attribute
            openssl_config = self.ssl_dir / "openssl.conf"
            self._create_openssl_config(openssl_config)
            
            # Private Key generieren
            key_cmd = [
                "openssl", "genrsa",
                "-out", str(self.key_file),
                "2048"
            ]
            
            result = subprocess.run(key_cmd, capture_output=True, text=True)
            if result.returncode != 0:
                ssl_logger.error(f"Private Key Generierung fehlgeschlagen: {result.stderr}")
                return False
                
            # Selbstsigniertes Zertifikat erstellen
            cert_cmd = [
                "openssl", "req",
                "-new", "-x509",
                "-key", str(self.key_file),
                "-out", str(self.cert_file),
                "-days", "365",
                "-config", str(openssl_config),
                "-extensions", "v3_req"
            ]
            
            result = subprocess.run(cert_cmd, capture_output=True, text=True)
            if result.returncode != 0:
                ssl_logger.error(f"Zertifikat-Generierung fehlgeschlagen: {result.stderr}")
                return False
                
            # Berechtigungen setzen
            os.chmod(self.key_file, 0o600)  # Nur Root kann lesen
            os.chmod(self.cert_file, 0o644)  # Alle können lesen
            
            # Zertifikat zu System CA-Store hinzufügen
            self._add_to_system_ca_store()
            
            ssl_logger.info(f"SSL-Zertifikat erfolgreich generiert:")
            ssl_logger.info(f"  Zertifikat: {self.cert_file}")
            ssl_logger.info(f"  Private Key: {self.key_file}")
            
            # Aufräumen
            openssl_config.unlink(missing_ok=True)
            
            return True
            
        except Exception as e:
            ssl_logger.error(f"Fehler bei SSL-Zertifikat-Generierung: {e}")
            return False
            
    def _create_openssl_config(self, config_path):
        """Erstellt OpenSSL-Konfigurationsdatei für erweiterte Zertifikat-Attribute"""
        config_content = """[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[req_distinguished_name]
C = DE
ST = Baden-Wuerttemberg
L = Stuttgart
O = Mercedes-Benz
OU = MYP Druckerverwaltung
CN = localhost

[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = *.localhost
DNS.3 = 127.0.0.1
IP.1 = 127.0.0.1
IP.2 = 0.0.0.0
"""
        
        with open(config_path, 'w', encoding='utf-8') as f:
            f.write(config_content)
            
    def _add_to_system_ca_store(self):
        """Fügt das Zertifikat zum System CA-Store hinzu (nur Linux)"""
        try:
            if os.name != 'posix':
                ssl_logger.info("System CA-Store Update nur unter Linux verfügbar")
                return
                
            system_cert_path = Path("/usr/local/share/ca-certificates/localhost.crt")
            
            # Kopiere Zertifikat in System CA-Store
            subprocess.run([
                "cp", str(self.cert_file), str(system_cert_path)
            ], check=True)
            
            # Aktualisiere CA-Zertifikate
            subprocess.run(["update-ca-certificates"], check=True)
            
            ssl_logger.info("Zertifikat erfolgreich zu System CA-Store hinzugefügt")
            
        except subprocess.CalledProcessError as e:
            ssl_logger.warning(f"System CA-Store Update fehlgeschlagen: {e}")
        except Exception as e:
            ssl_logger.warning(f"Unerwarteter Fehler beim CA-Store Update: {e}")
            
    def is_certificate_valid(self):
        """
        Prüft ob das vorhandene Zertifikat gültig ist
        
        Returns:
            bool: True wenn Zertifikat existiert und gültig ist
        """
        try:
            if not (self.cert_file.exists() and self.key_file.exists()):
                return False
                
            # Prüfe Zertifikat-Gültigkeit mit OpenSSL
            result = subprocess.run([
                "openssl", "x509",
                "-in", str(self.cert_file),
                "-noout", "-checkend", "86400"  # Prüfe ob in nächsten 24h abläuft
            ], capture_output=True)
            
            if result.returncode == 0:
                ssl_logger.info("Vorhandenes SSL-Zertifikat ist gültig")
                return True
            else:
                ssl_logger.info("Vorhandenes SSL-Zertifikat ist abgelaufen oder ungültig")
                return False
                
        except Exception as e:
            ssl_logger.warning(f"Zertifikat-Validierung fehlgeschlagen: {e}")
            return False
            
    def get_ssl_context(self):
        """
        Erstellt SSL-Kontext für Flask-Anwendung
        
        Returns:
            ssl.SSLContext oder tuple: SSL-Kontext oder Pfad-Tupel für Flask
        """
        try:
            # Stelle sicher, dass Zertifikate existieren
            if not self.is_certificate_valid():
                if not self.generate_ssl_certificate():
                    ssl_logger.error("SSL-Zertifikat-Generierung fehlgeschlagen")
                    return None
                    
            # Erstelle SSL-Kontext
            context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
            context.load_cert_chain(str(self.cert_file), str(self.key_file))
            
            # Sicherheitseinstellungen für Produktionsumgebung
            context.check_hostname = False
            context.verify_mode = ssl.CERT_NONE
            context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
            
            ssl_logger.info("SSL-Kontext erfolgreich erstellt")
            return context
            
        except Exception as e:
            ssl_logger.error(f"SSL-Kontext-Erstellung fehlgeschlagen: {e}")
            # Fallback: Rückgabe als Tupel für Flask
            if self.cert_file.exists() and self.key_file.exists():
                return (str(self.cert_file), str(self.key_file))
            return None

# Globale SSL-Manager-Instanz
_ssl_manager = None

def get_ssl_manager(app_dir="/opt/myp"):
    """
    Singleton-Pattern für SSL-Manager
    
    Args:
        app_dir (str): Anwendungsverzeichnis
        
    Returns:
        SSLCertificateManager: SSL-Manager-Instanz
    """
    global _ssl_manager
    if _ssl_manager is None:
        _ssl_manager = SSLCertificateManager(app_dir)
    return _ssl_manager

def get_ssl_context(app_dir="/opt/myp"):
    """
    Convenience-Funktion für SSL-Kontext
    
    Args:
        app_dir (str): Anwendungsverzeichnis
        
    Returns:
        ssl.SSLContext oder tuple: SSL-Kontext für Flask
    """
    manager = get_ssl_manager(app_dir)
    return manager.get_ssl_context()

def ensure_ssl_certificates(app_dir="/opt/myp", force_regenerate=False):
    """
    Stellt sicher, dass SSL-Zertifikate vorhanden sind
    
    Args:
        app_dir (str): Anwendungsverzeichnis
        force_regenerate (bool): Erzwingt Neugenerierung
        
    Returns:
        bool: True wenn erfolgreich
    """
    manager = get_ssl_manager(app_dir)
    return manager.generate_ssl_certificate(force_regenerate)

# Automatische Zertifikat-Generierung beim Import (nur wenn als Hauptmodul ausgeführt)
if __name__ == "__main__":
    import sys
    
    app_dir = sys.argv[1] if len(sys.argv) > 1 else "/opt/myp"
    force = "--force" in sys.argv
    
    print(f"Generiere SSL-Zertifikate für: {app_dir}")
    
    if ensure_ssl_certificates(app_dir, force):
        print("✅ SSL-Zertifikate erfolgreich generiert")
        sys.exit(0)
    else:
        print("❌ SSL-Zertifikat-Generierung fehlgeschlagen")
        sys.exit(1) 