"""Asynchrone ffprobe Media-Analyse"""
import asyncio
import json
import logging
import os
from typing import Optional
from app.models.media import MediaFile, VideoStream, AudioStream, SubtitleStream


class ProbeService:
    """ffprobe-basierte Media-Analyse - vollstaendig asynchron"""

    @staticmethod
    async def analyze(file_path: str) -> Optional[MediaFile]:
        """
        Analysiert eine Mediendatei mit ffprobe.
        Fuehrt 3 Aufrufe parallel aus (Video/Audio/Subtitle).
        Gibt None zurueck bei Fehler.
        """
        if not os.path.exists(file_path):
            logging.error(f"Datei nicht gefunden: {file_path}")
            return None

        try:
            # Alle drei Stream-Typen parallel abfragen
            video_task = ProbeService._probe_streams(file_path, "v")
            audio_task = ProbeService._probe_streams(file_path, "a")
            subtitle_task = ProbeService._probe_streams(file_path, "s")

            video_data, audio_data, subtitle_data = await asyncio.gather(
                video_task, audio_task, subtitle_task
            )

            # Streams parsen
            video_streams = ProbeService._parse_video_streams(video_data)
            audio_streams = ProbeService._parse_audio_streams(audio_data)
            subtitle_streams = ProbeService._parse_subtitle_streams(subtitle_data)

            # Format-Informationen aus Video-Abfrage
            size_bytes, duration_sec, bitrate = ProbeService._parse_format(video_data)

            media = MediaFile(
                source_path=file_path,
                source_size_bytes=size_bytes,
                source_duration_sec=duration_sec,
                source_bitrate=bitrate,
                video_streams=video_streams,
                audio_streams=audio_streams,
                subtitle_streams=subtitle_streams,
            )

            logging.info(
                f"Analysiert: {media.source_filename} "
                f"({media.source_size_human[0]} {media.source_size_human[1]}, "
                f"{MediaFile.format_time(duration_sec)}, "
                f"{len(video_streams)}V/{len(audio_streams)}A/{len(subtitle_streams)}S)"
            )
            return media

        except Exception as e:
            logging.error(f"ffprobe Analyse fehlgeschlagen fuer {file_path}: {e}")
            return None

    @staticmethod
    async def _probe_streams(file_path: str, stream_type: str) -> dict:
        """
        Einzelner ffprobe-Aufruf (async).
        stream_type: 'v' (Video), 'a' (Audio), 's' (Subtitle)
        """
        command = [
            "ffprobe", "-v", "error",
            "-select_streams", stream_type,
            "-show_entries",
            "stream=index,channels,codec_name,codec_type,pix_fmt,level,"
            "r_frame_rate,bit_rate,sample_rate,width,height"
            ":stream_tags=language",
            "-show_entries",
            "format=size,bit_rate,nb_streams,duration",
            "-of", "json",
            file_path,
        ]

        process = await asyncio.create_subprocess_exec(
            *command,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        stdout, stderr = await process.communicate()

        if process.returncode != 0:
            logging.warning(
                f"ffprobe Fehler (stream={stream_type}): "
                f"{stderr.decode(errors='replace').strip()}"
            )
            return {"streams": [], "format": {}}

        try:
            return json.loads(stdout.decode())
        except json.JSONDecodeError:
            logging.error(f"ffprobe JSON-Parsing fehlgeschlagen (stream={stream_type})")
            return {"streams": [], "format": {}}

    @staticmethod
    def _parse_video_streams(data: dict) -> list[VideoStream]:
        """Parst ffprobe JSON in VideoStream-Objekte"""
        streams = []
        for s in data.get("streams", []):
            # Framerate berechnen (z.B. "24000/1001" -> 23.976)
            fr_str = s.get("r_frame_rate", "0/1")
            parts = fr_str.split("/")
            if len(parts) == 2 and int(parts[1]) > 0:
                frame_rate = round(int(parts[0]) / int(parts[1]), 3)
            else:
                frame_rate = 0.0

            streams.append(VideoStream(
                index=s.get("index", 0),
                codec_name=s.get("codec_name", "unknown"),
                width=s.get("width", 0),
                height=s.get("height", 0),
                pix_fmt=s.get("pix_fmt", ""),
                frame_rate=frame_rate,
                level=s.get("level"),
                bit_rate=int(s["bit_rate"]) if s.get("bit_rate") else None,
            ))
        return streams

    @staticmethod
    def _parse_audio_streams(data: dict) -> list[AudioStream]:
        """Parst ffprobe JSON in AudioStream-Objekte"""
        streams = []
        for s in data.get("streams", []):
            language = s.get("tags", {}).get("language")
            streams.append(AudioStream(
                index=s.get("index", 0),
                codec_name=s.get("codec_name", "unknown"),
                channels=s.get("channels", 2),
                sample_rate=int(s.get("sample_rate", 48000)),
                language=language,
                bit_rate=int(s["bit_rate"]) if s.get("bit_rate") else None,
            ))
        return streams

    @staticmethod
    def _parse_subtitle_streams(data: dict) -> list[SubtitleStream]:
        """Parst ffprobe JSON in SubtitleStream-Objekte"""
        streams = []
        for s in data.get("streams", []):
            language = s.get("tags", {}).get("language")
            streams.append(SubtitleStream(
                index=s.get("index", 0),
                codec_name=s.get("codec_name", "unknown"),
                language=language,
            ))
        return streams

    @staticmethod
    def _parse_format(data: dict) -> tuple[int, float, int]:
        """Parst Format-Informationen: (size_bytes, duration_sec, bitrate)"""
        fmt = data.get("format", {})
        if isinstance(fmt, list):
            fmt = fmt[0] if fmt else {}

        size_bytes = int(fmt.get("size", 0))
        bitrate = int(fmt.get("bit_rate", 0))

        # Duration kann HH:MM:SS oder Sekunden sein
        duration_raw = fmt.get("duration", "0")
        try:
            if ":" in str(duration_raw):
                duration_sec = MediaFile.time_to_seconds(str(duration_raw))
            else:
                duration_sec = float(duration_raw)
        except (ValueError, TypeError):
            duration_sec = 0.0

        return size_bytes, duration_sec, bitrate
