"""FFmpeg / FFprobe interactions and command building."""

import subprocess
import json

from .constants import FORMAT_INFO
from .utils import format_time


def get_audio_duration(input_file: str) -> float:
    """
    Retrieve the total duration (in seconds) of the input file using ffprobe.

    Args:
        input_file: Path to the media file.

    Returns:
        Duration in seconds as a float.
    """
    cmd = [
        'ffprobe', '-v', 'error',
        '-show_entries', 'format=duration',
        '-of', 'default=noprint_wrappers=1:nokey=1',
        input_file
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, check=False)
    return float(result.stdout.strip())


def has_stream_type(input_file: str, stream_type: str) -> bool:
    """
    Check whether the input file contains a stream of the given type.

    Args:
        input_file: Path to the media file.
        stream_type: 'a' for audio, 'v' for video, 's' for subtitle.

    Returns:
        True if at least one such stream exists, False otherwise.
    """
    cmd = [
        'ffprobe', '-v', 'error',
        '-select_streams', stream_type,
        '-show_entries', 'stream=codec_type',
        '-of', 'default=noprint_wrappers=1:nokey=1',
        input_file
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, check=False)
    return bool(result.stdout.strip())


def get_audio_codec(input_file: str) -> str:
    """
    Return the codec name of the first audio stream.

    Args:
        input_file: Path to the media file.

    Returns:
        Codec name as a lowercase string, or None if no audio stream exists.
    """
    cmd = [
        'ffprobe', '-v', 'error',
        '-select_streams', 'a:0',
        '-show_entries', 'stream=codec_name',
        '-of', 'default=noprint_wrappers=1:nokey=1',
        input_file
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, check=False)
    codec = result.stdout.strip().lower()
    return codec if codec else None


def get_stream_info(input_file: str):
    """
    Collect information about the streams present in the input file.

    Args:
        input_file: Path to the media file.

    Returns:
        A dictionary with keys: has_audio, has_video, has_subtitle, audio_codec.
    """
    return {
        'has_audio': has_stream_type(input_file, 'a'),
        'has_video': has_stream_type(input_file, 'v'),
        'has_subtitle': has_stream_type(input_file, 's'),
        'audio_codec': get_audio_codec(input_file)
    }

def build_ffmpeg_command(input_file, start_seconds, duration_seconds, output_path,
                         stream_info, format_opt, transcode_audio,
                         drop_video, drop_subs, metadata=None):
    """
    Construct the FFmpeg command line as a list of arguments.

    Args:
        input_file: Path to the input media file.
        start_seconds: Start time for the segment (in seconds).
        duration_seconds: Duration of the segment (in seconds).
        output_path: Destination path for the output file.
        stream_info: Dictionary from get_stream_info().
        format_opt: Output container format (e.g., 'mp3').
        transcode_audio: Audio codec to transcode to (or None).
        drop_video: True to remove video streams.
        drop_subs: True to remove subtitle streams.
        metadata: Optional dict of metadata key/value pairs to write.

    Returns:
        A list of command‑line arguments suitable for subprocess.run().
    """
    cmd = [
        'ffmpeg',
        '-i', input_file,
        '-ss', format_time(start_seconds),
        '-t', format_time(duration_seconds)
    ]

    # -------------------- Clear all original metadata --------------------
    cmd.append('-map_metadata')
    cmd.append('-1')

    # -------------------- Apply custom metadata --------------------
    if metadata:
        for key, value in metadata.items():
            if value is not None and value != '':
                cmd.extend(['-metadata', f"{key}={value}"])

    # -------------------- Stream mapping --------------------
    # Map the streams we want to keep.
    if drop_video and drop_subs:
        cmd.extend(['-map', '0:a:0'])
    elif drop_video:
        cmd.extend(['-map', '0:a:0', '-map', '0:s?'])
    elif drop_subs:
        cmd.extend(['-map', '0:a:0', '-map', '0:v:0'])
    else:
        cmd.extend(['-map', '0'])

    # -------------------- Audio codec --------------------
    if transcode_audio:
        cmd.extend(['-c:a', transcode_audio])
        if transcode_audio in ('libmp3lame', 'mp3'):
            cmd.extend(['-b:a', '192k'])
        elif transcode_audio in ('libopus', 'opus'):
            cmd.extend(['-b:a', '128k'])
    else:
        cmd.extend(['-c:a', 'copy'])

    # -------------------- Video codec --------------------
    if not drop_video and stream_info['has_video']:
        cmd.extend(['-c:v', 'copy'])
    else:
        cmd.append('-vn')

    # -------------------- Subtitle codec --------------------
    if not drop_subs and stream_info['has_subtitle']:
        cmd.extend(['-c:s', 'copy'])
    else:
        cmd.append('-sn')

    # -------------------- Output format --------------------
    if format_opt:
        ffmpeg_format = FORMAT_INFO.get(format_opt, {}).get('ffmpeg', format_opt)
        cmd.extend(['-f', ffmpeg_format])

    # Overwrite output if it already exists.
    cmd.extend(['-y', output_path])

    return cmd

def get_metadata(input_file: str) -> dict:
    """
    Retrieve metadata from the input file using ffprobe with JSON output.
    Returns a dict with:
        - album: merged from all sources (last wins)
        - title: merged from all sources (last wins)
        - comments: list of (stream_index, comment) tuples
    """
    cmd = [
        'ffprobe', '-v', 'quiet',
        '-print_format', 'json',
        '-show_entries', 'format_tags:stream_tags',
        input_file
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, check=False)

    if result.returncode != 0:
        return {'album': None, 'title': None, 'comments': []}

    try:
        data = json.loads(result.stdout)
        # Collect album and title (merged, last wins).
        album = None
        title = None
        comments = []  # list of (stream_index, comment)

        # Format tags.
        fmt_tags = data.get('format', {}).get('tags', {})
        album = fmt_tags.get('album') or album
        title = fmt_tags.get('title') or title
        # Format does not have a stream index; we'll treat it as -1 if needed.

        # Stream tags.
        for idx, stream in enumerate(data.get('streams', [])):
            stream_tags = stream.get('tags', {})
            # Album and title: update if present.
            if 'album' in stream_tags:
                album = stream_tags['album']
            if 'title' in stream_tags:
                title = stream_tags['title']
            # Comment: collect all occurrences.
            if 'comment' in stream_tags:
                comments.append((idx, stream_tags['comment']))

        return {
            'album': album,
            'title': title,
            'comments': comments
        }
    except (json.JSONDecodeError, KeyError):
        return {'album': None, 'title': None, 'comments': []}

