"""Audio format conversion using ffmpeg (Phase 3 — stub + basic implementation)."""

from __future__ import annotations

import asyncio
import shutil
import subprocess
from pathlib import Path

import structlog

from xc_dl.core.manifest import Manifest
from xc_dl.core.verifier import compute_sha256
from xc_dl.utils import build_filename, build_relative_dir

logger = structlog.get_logger()


def check_ffmpeg() -> bool:
    """Check if ffmpeg is available."""
    return shutil.which("ffmpeg") is not None


async def convert_one(
    src: Path,
    dst: Path,
    sample_rate: int = 16000,
    channels: int = 1,
    bit_depth: int = 16,
) -> bool:
    """Convert a single audio file using ffmpeg."""
    dst.parent.mkdir(parents=True, exist_ok=True)

    sample_fmt = "s16" if bit_depth == 16 else "s32"
    cmd = [
        "ffmpeg",
        "-v", "error",
        "-i", str(src),
        "-ar", str(sample_rate),
        "-ac", str(channels),
        "-sample_fmt", sample_fmt,
        "-y",
        str(dst),
    ]

    def _run() -> bool:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
        if result.returncode != 0:
            logger.error(
                "conversion_failed",
                src=str(src),
                stderr=result.stderr[:200],
            )
            dst.unlink(missing_ok=True)
            return False
        return True

    return await asyncio.get_event_loop().run_in_executor(None, _run)


async def convert_recordings(
    entries: list,
    data_dir: Path,
    target_format: str = "wav",
    sample_rate: int = 16000,
    channels: int = 1,
    bit_depth: int = 16,
    concurrency: int = 4,
    progress_callback: object = None,
) -> tuple[int, int]:
    """Convert downloaded recordings to a uniform format.

    Returns (succeeded, failed) counts.
    """
    if not check_ffmpeg():
        logger.error("ffmpeg_not_found", message="ffmpeg is required for conversion. Install it and retry.")
        raise RuntimeError("ffmpeg not found")

    rate_label = f"{sample_rate // 1000}khz"
    resampled_dir_name = f"resampled_{rate_label}"
    semaphore = asyncio.Semaphore(concurrency)
    manifest = Manifest(data_dir)

    succeeded = 0
    failed = 0

    async def _convert_entry(entry: object) -> bool:
        nonlocal succeeded, failed
        src = data_dir / entry.original_path  # type: ignore[union-attr]
        if not src.exists():
            failed += 1
            return False

        # Build resampled path
        rel_dir = build_relative_dir(entry.family, entry.gen, entry.sp)  # type: ignore[union-attr]
        dst_name = build_filename(entry.xc_id, entry.gen, entry.sp, target_format)  # type: ignore[union-attr]
        dst = data_dir / resampled_dir_name / rel_dir / dst_name

        if dst.exists():
            succeeded += 1
            if progress_callback:
                await progress_callback()  # type: ignore[misc]
            return True

        async with semaphore:
            ok = await convert_one(src, dst, sample_rate, channels, bit_depth)
            if ok:
                sha256 = await compute_sha256(dst)
                resampled_path = str(Path(resampled_dir_name) / rel_dir / dst_name)
                manifest.update_entry(
                    entry.xc_id,  # type: ignore[union-attr]
                    resampled_path=resampled_path,
                    sha256_resampled=sha256,
                    converted_from=entry.original_format,  # type: ignore[union-attr]
                    sample_rate=sample_rate,
                    channels=channels,
                )
                succeeded += 1
            else:
                failed += 1
            if progress_callback:
                await progress_callback()  # type: ignore[misc]
            return ok

    tasks = [_convert_entry(e) for e in entries]
    await asyncio.gather(*tasks, return_exceptions=True)

    logger.info("conversion_summary", succeeded=succeeded, failed=failed)
    return succeeded, failed
