"""Audio-video synchronization utilities."""

import logging
import subprocess
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)


def get_video_duration(video_path: str | Path) -> float | None:
    """Get video duration using ffprobe.

    Args:
        video_path: Path to video file

    Returns:
        Duration in seconds, or None if failed
    """
    try:
        result = subprocess.run(
            [
                "ffprobe",
                "-v",
                "error",
                "-show_entries",
                "format=duration",
                "-of",
                "default=noprint_wrappers=1:nokey=1",
                str(video_path),
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )

        if result.returncode == 0:
            return float(result.stdout.strip())
        else:
            logger.error(f"ffprobe error: {result.stderr}")
            return None
    except Exception as e:
        logger.error(f"Failed to get video duration: {e}")
        return None


def sync_audio_to_video(
    video_path: str | Path,
    audio_path: str | Path,
    output_path: str | Path | None = None,
    audio_volume: float = 1.0,
    fade_in: float = 0.0,
    fade_out: float = 0.0,
) -> dict[str, Any]:
    """Combine audio with video, syncing durations.

    Args:
        video_path: Path to video file
        audio_path: Path to audio file
        output_path: Output path (auto-generated if None)
        audio_volume: Volume multiplier for audio (1.0 = original)
        fade_in: Audio fade-in duration in seconds
        fade_out: Audio fade-out duration in seconds

    Returns:
        Dict with output path and metadata
    """
    video_path = Path(video_path)
    audio_path = Path(audio_path)

    if output_path is None:
        output_path = video_path.parent / f"{video_path.stem}_narrated{video_path.suffix}"
    else:
        output_path = Path(output_path)

    # Get durations
    video_duration = get_video_duration(video_path)
    audio_duration = get_video_duration(audio_path)

    if video_duration is None:
        return {"success": False, "error": "Could not determine video duration"}

    logger.info(f"Video: {video_duration:.2f}s, Audio: {audio_duration:.2f}s")

    # Build ffmpeg filter for audio processing
    audio_filters = []

    # Volume adjustment
    if audio_volume != 1.0:
        audio_filters.append(f"volume={audio_volume}")

    # Speed adjustment to match video duration
    if audio_duration and abs(audio_duration - video_duration) > 0.5:
        speed_factor = audio_duration / video_duration
        # atempo only supports 0.5-2.0, chain if needed
        if 0.5 <= speed_factor <= 2.0:
            audio_filters.append(f"atempo={speed_factor}")
        elif speed_factor > 2.0:
            # Chain multiple atempo filters
            while speed_factor > 2.0:
                audio_filters.append("atempo=2.0")
                speed_factor /= 2.0
            if speed_factor > 1.0:
                audio_filters.append(f"atempo={speed_factor}")
        else:
            # speed_factor < 0.5
            while speed_factor < 0.5:
                audio_filters.append("atempo=0.5")
                speed_factor *= 2.0
            if speed_factor < 1.0:
                audio_filters.append(f"atempo={speed_factor}")

    # Fade effects
    if fade_in > 0:
        audio_filters.append(f"afade=t=in:st=0:d={fade_in}")
    if fade_out > 0 and video_duration:
        audio_filters.append(f"afade=t=out:st={video_duration - fade_out}:d={fade_out}")

    # Build ffmpeg command
    cmd = [
        "ffmpeg",
        "-y",  # Overwrite output
        "-i",
        str(video_path),
        "-i",
        str(audio_path),
    ]

    if audio_filters:
        cmd.extend(["-af", ",".join(audio_filters)])

    cmd.extend(
        [
            "-c:v",
            "copy",  # Copy video stream without re-encoding
            "-c:a",
            "aac",  # Encode audio as AAC
            "-b:a",
            "192k",  # Audio bitrate
            "-shortest",  # Match shortest stream duration
            str(output_path),
        ]
    )

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=300,  # 5 minute timeout
        )

        if result.returncode == 0:
            output_duration = get_video_duration(output_path)
            return {
                "success": True,
                "path": str(output_path),
                "video_duration": video_duration,
                "audio_duration": audio_duration,
                "output_duration": output_duration,
                "audio_adjusted": bool(audio_filters),
            }
        else:
            return {
                "success": False,
                "error": result.stderr,
                "command": " ".join(cmd),
            }
    except subprocess.TimeoutExpired:
        return {"success": False, "error": "ffmpeg timed out"}
    except FileNotFoundError:
        return {"success": False, "error": "ffmpeg not found. Please install ffmpeg."}
    except Exception as e:
        return {"success": False, "error": str(e)}


def add_background_music(
    video_path: str | Path,
    music_path: str | Path,
    output_path: str | Path | None = None,
    music_volume: float = 0.3,
    duck_volume: float = 0.1,  # noqa: ARG001 - Reserved for future sidechain ducking
    has_narration: bool = False,
) -> dict[str, Any]:
    """Add background music to video, optionally ducking during narration.

    Args:
        video_path: Path to video file (may already have narration)
        music_path: Path to music file
        output_path: Output path
        music_volume: Base volume for music (0-1)
        duck_volume: Volume when ducking for narration (0-1)
        has_narration: Whether video has narration to duck under

    Returns:
        Dict with output path and metadata
    """
    video_path = Path(video_path)
    music_path = Path(music_path)

    if output_path is None:
        output_path = video_path.parent / f"{video_path.stem}_with_music{video_path.suffix}"
    else:
        output_path = Path(output_path)

    video_duration = get_video_duration(video_path)

    if video_duration is None:
        return {"success": False, "error": "Could not determine video duration"}

    # Build ffmpeg command
    if has_narration:
        # Complex filter to mix narration and music with sidechaining
        filter_complex = (
            f"[1:a]volume={music_volume}[music];"
            f"[0:a][music]amix=inputs=2:duration=first:dropout_transition=2[out]"
        )
        cmd = [
            "ffmpeg",
            "-y",
            "-i",
            str(video_path),
            "-stream_loop",
            "-1",  # Loop music
            "-i",
            str(music_path),
            "-filter_complex",
            filter_complex,
            "-map",
            "0:v",
            "-map",
            "[out]",
            "-c:v",
            "copy",
            "-c:a",
            "aac",
            "-t",
            str(video_duration),
            str(output_path),
        ]
    else:
        # Simple music addition
        cmd = [
            "ffmpeg",
            "-y",
            "-i",
            str(video_path),
            "-stream_loop",
            "-1",
            "-i",
            str(music_path),
            "-c:v",
            "copy",
            "-c:a",
            "aac",
            "-af",
            f"volume={music_volume}",
            "-t",
            str(video_duration),
            "-map",
            "0:v",
            "-map",
            "1:a",
            str(output_path),
        ]

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)

        if result.returncode == 0:
            return {
                "success": True,
                "path": str(output_path),
                "duration": video_duration,
            }
        else:
            return {"success": False, "error": result.stderr}
    except Exception as e:
        return {"success": False, "error": str(e)}
