"""Konfigurationsmanagement - Singleton fuer Settings und Presets

Alle wichtigen Settings koennen per Umgebungsvariable ueberschrieben werden.
ENV-Variablen haben IMMER Vorrang vor settings.yaml.

Mapping (VK_ Prefix):
  Datenbank:  VK_DB_HOST, VK_DB_PORT, VK_DB_USER, VK_DB_PASSWORD, VK_DB_NAME
  Encoding:   VK_MODE (cpu/gpu/auto), VK_GPU_DEVICE, VK_MAX_JOBS, VK_DEFAULT_PRESET
  Server:     VK_PORT, VK_HOST, VK_EXTERNAL_URL
  Library:    VK_TVDB_API_KEY, VK_TVDB_LANGUAGE, VK_LIBRARY_ENABLED (true/false)
  Dateien:    VK_TARGET_CONTAINER (webm/mkv/mp4)
  Logging:    VK_LOG_LEVEL (DEBUG/INFO/WARNING/ERROR)
  TV-App:     VK_TV_WATCHED_THRESHOLD, VK_TV_HLS_SEGMENT_SEC, VK_TV_HLS_TIMEOUT_MIN,
              VK_TV_HLS_MAX_SESSIONS, VK_TV_PAUSE_BATCH
"""
import os
import logging
import yaml
from pathlib import Path
from typing import Optional
from logging.handlers import TimedRotatingFileHandler, RotatingFileHandler

# Mapping: ENV-Variable -> (settings-pfad, typ)
# Pfad als Tuple: ("section", "key")
_ENV_MAP: dict[str, tuple[tuple[str, str], type]] = {
    "VK_DB_HOST":          (("database", "host"), str),
    "VK_DB_PORT":          (("database", "port"), int),
    "VK_DB_USER":          (("database", "user"), str),
    "VK_DB_PASSWORD":      (("database", "password"), str),
    "VK_DB_NAME":          (("database", "database"), str),
    "VK_MODE":             (("encoding", "mode"), str),
    "VK_GPU_DEVICE":       (("encoding", "gpu_device"), str),
    "VK_MAX_JOBS":         (("encoding", "max_parallel_jobs"), int),
    "VK_DEFAULT_PRESET":   (("encoding", "default_preset"), str),
    "VK_PORT":             (("server", "port"), int),
    "VK_HOST":             (("server", "host"), str),
    "VK_EXTERNAL_URL":     (("server", "external_url"), str),
    "VK_TVDB_API_KEY":     (("library", "tvdb_api_key"), str),
    "VK_TVDB_LANGUAGE":    (("library", "tvdb_language"), str),
    "VK_LIBRARY_ENABLED":  (("library", "enabled"), bool),
    "VK_TARGET_CONTAINER": (("files", "target_container"), str),
    "VK_LOG_LEVEL":        (("logging", "level"), str),
    "VK_TV_WATCHED_THRESHOLD": (("tv", "watched_threshold_pct"), int),
    "VK_TV_HLS_SEGMENT_SEC":   (("tv", "hls_segment_duration"), int),
    "VK_TV_HLS_TIMEOUT_MIN":   (("tv", "hls_session_timeout_min"), int),
    "VK_TV_HLS_MAX_SESSIONS":  (("tv", "hls_max_sessions"), int),
    "VK_TV_PAUSE_BATCH":       (("tv", "pause_batch_on_stream"), bool),
}

# Rueckwaertskompatibilitaet
_ENV_ALIASES: dict[str, str] = {
    "VIDEO_KONVERTER_MODE": "VK_MODE",
}

# Default-Settings wenn keine settings.yaml existiert
_DEFAULT_SETTINGS: dict = {
    "database": {
        "host": "localhost",
        "port": 3306,
        "user": "video",
        "password": "",
        "database": "video_converter",
    },
    "encoding": {
        "mode": "cpu",
        "default_preset": "cpu_av1",
        "gpu_device": "/dev/dri/renderD128",
        "gpu_driver": "iHD",
        "max_parallel_jobs": 1,
    },
    "server": {
        "host": "0.0.0.0",
        "port": 8080,
        "external_url": "",
        "use_https": False,
        "websocket_path": "/ws",
    },
    "files": {
        "delete_source": False,
        "recursive_scan": True,
        "scan_extensions": [".mkv", ".mp4", ".avi", ".wmv", ".vob", ".ts", ".m4v", ".flv", ".mov"],
        "target_container": "webm",
        "target_folder": "same",
    },
    "audio": {
        "bitrate_map": {2: "128k", 6: "320k", 8: "450k"},
        "default_bitrate": "192k",
        "default_codec": "libopus",
        "keep_channels": True,
        "languages": ["ger", "eng", "und"],
    },
    "subtitle": {
        "codec_blacklist": ["hdmv_pgs_subtitle", "dvd_subtitle", "dvb_subtitle"],
        "languages": ["ger", "eng"],
    },
    "library": {
        "enabled": True,
        "import_default_mode": "copy",
        "import_naming_pattern": "{series} - S{season:02d}E{episode:02d} - {title}.{ext}",
        "import_season_pattern": "Season {season:02d}",
        "scan_interval_hours": 0,
        "tvdb_api_key": "",
        "tvdb_language": "deu",
        "tvdb_pin": "",
    },
    "tv": {
        "watched_threshold_pct": 90,
        "hls_segment_duration": 4,
        "hls_init_duration": 1,
        "hls_session_timeout_min": 5,
        "hls_max_sessions": 5,
        "pause_batch_on_stream": True,
    },
    "cleanup": {
        "enabled": False,
        "delete_extensions": [".avi", ".wmv", ".vob", ".nfo", ".txt", ".jpg", ".png", ".srt", ".sub", ".idx"],
        "keep_extensions": [".srt"],
        "exclude_patterns": ["readme*", "*.md"],
    },
    "logging": {
        "level": "INFO",
        "file": "server.log",
        "rotation": "time",
        "backup_count": 7,
        "max_size_mb": 10,
    },
    "statistics": {
        "cleanup_days": 365,
        "max_entries": 5000,
    },
}


class Config:
    """Laedt und verwaltet settings.yaml und presets.yaml.
    ENV-Variablen (VK_*) ueberschreiben YAML-Werte."""
    _instance: Optional['Config'] = None

    def __new__(cls) -> 'Config':
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self) -> None:
        if self._initialized:
            return
        self._initialized = True

        self._base_path = Path(__file__).parent
        self._cfg_path = self._base_path / "cfg"
        self._log_path = self._base_path.parent / "logs"
        self._data_path = self._base_path.parent / "data"

        # Verzeichnisse sicherstellen
        self._cfg_path.mkdir(parents=True, exist_ok=True)
        self._log_path.mkdir(parents=True, exist_ok=True)
        self._data_path.mkdir(parents=True, exist_ok=True)

        self.settings: dict = {}
        self.presets: dict = {}
        self._load_settings()
        self._load_presets()
        self._apply_env_overrides()

    @property
    def log_file_path(self) -> str:
        """Pfad zur aktiven Log-Datei"""
        log_file = self.settings.get("logging", {}).get("file", "server.log")
        return str(self._log_path / log_file)

    def _load_settings(self) -> None:
        """Laedt settings.yaml oder erzeugt Defaults"""
        import copy
        settings_file = self._cfg_path / "settings.yaml"
        if settings_file.exists():
            try:
                with open(settings_file, "r", encoding="utf-8") as f:
                    self.settings = yaml.safe_load(f) or {}
                logging.info(f"Settings geladen: {settings_file}")
            except Exception as e:
                logging.error(f"Settings lesen fehlgeschlagen: {e}")
                self.settings = copy.deepcopy(_DEFAULT_SETTINGS)
        else:
            # Keine settings.yaml -> Defaults verwenden und speichern
            logging.info("Keine settings.yaml gefunden - erzeuge Defaults")
            self.settings = copy.deepcopy(_DEFAULT_SETTINGS)
            self._save_yaml(settings_file, self.settings)

    def _load_presets(self) -> None:
        """Laedt presets.yaml. Falls nicht vorhanden, aus cfg_defaults kopieren."""
        presets_file = self._cfg_path / "presets.yaml"
        if not presets_file.exists():
            # Versuche Default-Presets aus cfg_defaults zu kopieren
            defaults_file = Path(__file__).parent.parent / "cfg_defaults" / "presets.yaml"
            if defaults_file.exists():
                import shutil
                shutil.copy2(defaults_file, presets_file)
                logging.info(f"Default-Presets kopiert: {defaults_file} -> {presets_file}")
            else:
                logging.warning("Keine presets.yaml und keine Defaults gefunden")

        if presets_file.exists():
            try:
                with open(presets_file, "r", encoding="utf-8") as f:
                    self.presets = yaml.safe_load(f) or {}
                logging.info(f"Presets geladen: {presets_file}")
            except Exception as e:
                logging.error(f"Presets lesen fehlgeschlagen: {e}")
                self.presets = {}
        else:
            self.presets = {}

    def _apply_env_overrides(self) -> None:
        """Umgebungsvariablen (VK_*) ueberschreiben Settings.
        Unterstuetzt auch alte Variablennamen per Alias-Mapping."""
        applied = []

        # Aliase aufloesen (z.B. VIDEO_KONVERTER_MODE -> VK_MODE)
        for old_name, new_name in _ENV_ALIASES.items():
            if old_name in os.environ and new_name not in os.environ:
                os.environ[new_name] = os.environ[old_name]

        for env_key, ((section, key), val_type) in _ENV_MAP.items():
            raw = os.environ.get(env_key)
            if raw is None or raw == "":
                continue  # Leere ENV-Variablen ueberschreiben YAML nicht

            # Typ-Konvertierung
            try:
                if val_type is bool:
                    value = raw.lower() in ("true", "1", "yes", "on")
                elif val_type is int:
                    value = int(raw)
                else:
                    value = raw
            except (ValueError, TypeError):
                logging.warning(f"ENV {env_key}={raw!r} - ungueliger Wert, uebersprungen")
                continue

            self.settings.setdefault(section, {})[key] = value
            applied.append(f"{env_key}={value}")

        if applied:
            logging.info(f"ENV-Overrides angewendet: {', '.join(applied)}")

    @staticmethod
    def _save_yaml(path: Path, data: dict) -> None:
        """Schreibt dict als YAML in Datei"""
        try:
            with open(path, "w", encoding="utf-8") as f:
                yaml.dump(data, f, default_flow_style=False,
                          indent=2, allow_unicode=True)
            logging.info(f"YAML gespeichert: {path}")
        except Exception as e:
            logging.error(f"YAML speichern fehlgeschlagen ({path}): {e}")

    def save_settings(self) -> None:
        """Schreibt aktuelle Settings zurueck in settings.yaml"""
        self._save_yaml(self._cfg_path / "settings.yaml", self.settings)

    def save_presets(self) -> None:
        """Schreibt Presets zurueck in presets.yaml"""
        self._save_yaml(self._cfg_path / "presets.yaml", self.presets)

    def setup_logging(self) -> None:
        """Konfiguriert Logging mit Rotation"""
        log_cfg = self.settings.get("logging", {})
        log_level = log_cfg.get("level", "INFO")
        log_file = log_cfg.get("file", "server.log")
        log_mode = log_cfg.get("rotation", "time")
        backup_count = log_cfg.get("backup_count", 7)

        log_path = self._log_path / log_file
        handlers = [logging.StreamHandler()]

        if log_mode == "time":
            file_handler = TimedRotatingFileHandler(
                str(log_path), when="midnight", interval=1,
                backupCount=backup_count, encoding="utf-8"
            )
        else:
            max_bytes = log_cfg.get("max_size_mb", 10) * 1024 * 1024
            file_handler = RotatingFileHandler(
                str(log_path), maxBytes=max_bytes,
                backupCount=backup_count, encoding="utf-8"
            )

        handlers.append(file_handler)

        # force=True weil Config.__init__ logging aufruft bevor setup_logging()
        logging.basicConfig(
            level=getattr(logging, log_level, logging.INFO),
            format="%(asctime)s - %(levelname)s - %(message)s",
            handlers=handlers,
            force=True,
        )

    # --- Properties fuer haeufig benoetigte Werte ---

    @property
    def encoding_mode(self) -> str:
        return self.settings.get("encoding", {}).get("mode", "cpu")

    @property
    def gpu_device(self) -> str:
        return self.settings.get("encoding", {}).get("gpu_device", "/dev/dri/renderD128")

    @property
    def max_parallel_jobs(self) -> int:
        return self.settings.get("encoding", {}).get("max_parallel_jobs", 1)

    @property
    def target_container(self) -> str:
        return self.settings.get("files", {}).get("target_container", "webm")

    @property
    def default_preset_name(self) -> str:
        return self.settings.get("encoding", {}).get("default_preset", "cpu_av1")

    @property
    def default_preset(self) -> dict:
        name = self.default_preset_name
        return self.presets.get(name, {})

    @property
    def data_path(self) -> Path:
        return self._data_path

    @property
    def audio_config(self) -> dict:
        return self.settings.get("audio", {})

    @property
    def subtitle_config(self) -> dict:
        return self.settings.get("subtitle", {})

    @property
    def files_config(self) -> dict:
        return self.settings.get("files", {})

    @property
    def cleanup_config(self) -> dict:
        return self.settings.get("cleanup", {})

    @property
    def tv_config(self) -> dict:
        return self.settings.get("tv", {})

    @property
    def server_config(self) -> dict:
        return self.settings.get("server", {})
