"""ffmpeg Command Builder - GPU und CPU Encoding"""
import os
import logging
import asyncio
from app.config import Config
from app.models.job import ConversionJob


class EncoderService:
    """Baut ffmpeg-Befehle basierend auf Preset und Media-Analyse"""

    def __init__(self, config: Config):
        self.config = config

    def build_command(self, job: ConversionJob) -> list[str]:
        """
        Baut den vollstaendigen ffmpeg-Befehl.
        Beruecksichtigt GPU/CPU, Preset, Audio/Subtitle-Filter.
        """
        preset = self.config.presets.get(job.preset_name, {})
        if not preset:
            logging.error(f"Preset '{job.preset_name}' nicht gefunden")
            preset = self.config.default_preset

        cmd = ["ffmpeg", "-y"]

        # GPU-Initialisierung
        cmd.extend(self._build_hw_init(preset))

        # Input
        cmd.extend(["-i", job.media.source_path])

        # Video-Stream (erster Video-Stream)
        cmd.extend(self._build_video_params(job, preset))

        # Audio-Streams (alle passenden)
        cmd.extend(self._build_audio_params(job))

        # Subtitle-Streams (alle passenden)
        cmd.extend(self._build_subtitle_params(job))

        # Output
        cmd.append(job.target_path)

        job.ffmpeg_cmd = cmd
        return cmd

    def _build_hw_init(self, preset: dict) -> list[str]:
        """GPU-Initialisierung fuer VAAPI"""
        if not preset.get("hw_init", False):
            return []

        device = self.config.gpu_device
        return [
            "-init_hw_device", f"vaapi=intel:{device}",
            "-hwaccel", "vaapi",
            "-hwaccel_device", "intel",
        ]

    def _build_video_params(self, job: ConversionJob, preset: dict) -> list[str]:
        """Video-Parameter: Codec, Quality, Filter"""
        cmd = ["-map", "0:v:0"]  # Erster Video-Stream

        # Video-Codec
        codec = preset.get("video_codec", "libsvtav1")
        cmd.extend(["-c:v", codec])

        # Quality (CRF oder QP je nach Encoder)
        quality_param = preset.get("quality_param", "crf")
        quality_value = preset.get("quality_value", 30)
        cmd.extend([f"-{quality_param}", str(quality_value)])

        # GOP-Groesse
        gop = preset.get("gop_size")
        if gop:
            cmd.extend(["-g", str(gop)])

        # Speed-Preset (nur CPU-Encoder)
        speed = preset.get("speed_preset")
        if speed is not None:
            cmd.extend(["-preset", str(speed)])

        # Video-Filter
        vf = preset.get("video_filter", "")
        if not vf and preset.get("hw_init"):
            # Auto-Detect Pixel-Format fuer GPU
            vf = self._detect_gpu_filter(job)
        if vf:
            cmd.extend(["-vf", vf])

        # Extra-Parameter
        for key, value in preset.get("extra_params", {}).items():
            cmd.extend([f"-{key}", str(value)])

        return cmd

    def _build_audio_params(self, job: ConversionJob) -> list[str]:
        """
        Audio-Streams: Filtert nach Sprache, setzt Codec/Bitrate.
        WICHTIG: Kanalanzahl wird beibehalten (kein Downmix)!
        Surround (5.1/7.1) und Stereo (2.0/2.1) bleiben erhalten.
        """
        audio_cfg = self.config.audio_config
        languages = audio_cfg.get("languages", ["ger", "eng", "und"])
        codec = audio_cfg.get("default_codec", "libopus")
        bitrate_map = audio_cfg.get("bitrate_map", {2: "128k", 6: "320k", 8: "450k"})
        default_bitrate = audio_cfg.get("default_bitrate", "192k")
        keep_channels = audio_cfg.get("keep_channels", True)

        cmd = []
        audio_idx = 0

        for stream in job.media.audio_streams:
            # Sprachfilter: Wenn Sprache gesetzt, muss sie in der Liste sein
            lang = stream.language
            if lang and lang not in languages:
                continue

            cmd.extend(["-map", f"0:{stream.index}"])

            if codec == "copy":
                cmd.extend([f"-c:a:{audio_idx}", "copy"])
            else:
                cmd.extend([f"-c:a:{audio_idx}", codec])

                # Bitrate nach Kanalanzahl (Surround bekommt mehr)
                # Konvertiere bitrate_map Keys zu int (YAML laedt sie als int)
                channels = stream.channels
                bitrate = str(bitrate_map.get(channels, default_bitrate))
                cmd.extend([f"-b:a:{audio_idx}", bitrate])

                # Kanalanzahl beibehalten
                if keep_channels:
                    cmd.extend([f"-ac:{audio_idx}", str(channels)])

                # Channel-Layout normalisieren fuer libopus
                # EAC3/AC3 mit 5.1(side) Layout fuehrt zu Encoder-Fehler
                if codec == "libopus" and channels == 6:
                    cmd.extend([
                        f"-filter:a:{audio_idx}",
                        "channelmap=channel_layout=5.1",
                    ])

            audio_idx += 1

        return cmd

    def _build_subtitle_params(self, job: ConversionJob) -> list[str]:
        """Subtitle-Streams: Filtert nach Sprache und Blacklist"""
        sub_cfg = self.config.subtitle_config
        languages = sub_cfg.get("languages", ["ger", "eng"])
        blacklist = sub_cfg.get("codec_blacklist", [])

        cmd = []
        for stream in job.media.subtitle_streams:
            # Codec-Blacklist (Bild-basierte Untertitel)
            if stream.codec_name in blacklist:
                continue

            # Sprachfilter
            lang = stream.language
            if lang and lang not in languages:
                continue

            cmd.extend(["-map", f"0:{stream.index}"])

        # Subtitle-Codec: Bei WebM nur webvtt moeglich
        if job.target_container == "webm" and cmd:
            cmd.extend(["-c:s", "webvtt"])
        elif cmd:
            cmd.extend(["-c:s", "copy"])

        return cmd

    def _detect_gpu_filter(self, job: ConversionJob) -> str:
        """Erkennt Pixel-Format fuer GPU-Encoding"""
        if job.media.is_10bit:
            return "format=p010,hwupload"
        return "format=nv12,hwupload"

    @staticmethod
    def detect_gpu_available() -> bool:
        """Prueft ob GPU/VAAPI verfuegbar ist"""
        # Pruefe ob /dev/dri existiert
        if not os.path.exists("/dev/dri"):
            return False

        # Pruefe ob renderD* Devices vorhanden
        devices = EncoderService.get_available_render_devices()
        if not devices:
            return False

        return True

    @staticmethod
    def get_available_render_devices() -> list[str]:
        """Listet verfuegbare /dev/dri/renderD* Geraete"""
        dri_path = "/dev/dri"
        if not os.path.exists(dri_path):
            return []

        devices = []
        try:
            for entry in os.listdir(dri_path):
                if entry.startswith("renderD"):
                    devices.append(f"{dri_path}/{entry}")
        except PermissionError:
            logging.warning("Kein Zugriff auf /dev/dri")

        return sorted(devices)

    @staticmethod
    async def test_gpu_encoding(device: str = "/dev/dri/renderD128") -> bool:
        """Testet ob GPU-Encoding tatsaechlich funktioniert"""
        cmd = [
            "ffmpeg", "-y",
            "-init_hw_device", f"vaapi=test:{device}",
            "-f", "lavfi", "-i", "nullsrc=s=64x64:d=0.1",
            "-vf", "format=nv12,hwupload",
            "-c:v", "h264_vaapi",
            "-frames:v", "1",
            "-f", "null", "-",
        ]
        try:
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )
            await asyncio.wait_for(process.communicate(), timeout=10)
            return process.returncode == 0
        except Exception as e:
            logging.warning(f"GPU-Test fehlgeschlagen: {e}")
            return False
