"""Detection and extraction of embedded cover art from video files."""

import json
import os
import re
import shutil
import subprocess


def detect_embedded_image(
    video_path: str,
    ffprobe: str = "ffprobe",
    atomicparsley: str = "AtomicParsley",
) -> bool:
    """Return True if *video_path* contains an embedded cover art / attached picture.

    Uses two complementary strategies:

    1. **ffprobe**: checks for video streams with ``disposition.attached_pic == 1``
       (standard attached-picture stream, e.g. added by ffmpeg).
    2. **AtomicParsley fallback**: checks for a ``covr`` MP4 atom, which is how
       AtomicParsley and Apple tools embed artwork without creating a separate
       stream with the attached_pic flag.
    """
    # --- Strategy 1: ffprobe stream disposition ---
    # NOTE: disposition sub-fields require the colon separator in -show_entries,
    # i.e. "stream=...:stream_disposition=attached_pic".  The simpler
    # "stream=codec_name,disposition" form silently returns an empty dict.
    result = subprocess.run(
        [
            ffprobe,
            "-v", "error",
            "-select_streams", "v",
            "-show_entries", "stream=index,codec_name:stream_disposition=attached_pic",
            "-of", "json",
            video_path,
        ],
        capture_output=True,
        text=True,
    )
    if result.returncode == 0:
        try:
            data = json.loads(result.stdout)
            for stream in data.get("streams", []):
                disposition = stream.get("disposition", {})
                if disposition.get("attached_pic") == 1:
                    return True
        except json.JSONDecodeError:
            pass

    # --- Strategy 2: AtomicParsley covr atom ---
    # AtomicParsley embeds artwork in the MP4 'covr' atom, which does not set
    # the attached_pic stream disposition flag recognised by ffprobe.
    try:
        ap_result = subprocess.run(
            [atomicparsley, video_path, "-t"],
            capture_output=True,
            text=True,
        )
        if ap_result.returncode == 0:
            # Output contains lines like: Atom "covr" contains: 1 piece of artwork
            if re.search(r'Atom\s+"covr"\s+contains:\s*[1-9]', ap_result.stdout):
                return True
    except FileNotFoundError:
        pass  # AtomicParsley not installed – skip fallback

    return False


def extract_embedded_image(
    video_path: str,
    output_path: str,
    ffmpeg: str = "ffmpeg",
    ffprobe: str = "ffprobe",
    atomicparsley: str = "AtomicParsley",
) -> str:
    """Extract the embedded cover art from *video_path* to *output_path*.

    Returns the absolute path of the extracted image.
    Raises ``RuntimeError`` if no embedded image is found.

    Two extraction strategies are tried in order:

    1. **ffmpeg via attached-picture stream** – works for streams flagged with
       ``disposition.attached_pic == 1`` (standard ISO-base-media embedded art).
    2. **AtomicParsley ``--extractPix``** – fallback for artwork stored only in
       the MP4 ``covr`` atom (as embedded by AtomicParsley / Apple tools).
    """
    # Find the stream index of the attached picture.
    # NOTE: use the colon-separated show_entries syntax so that ffprobe actually
    # populates the 'disposition' sub-object in its JSON output.
    result = subprocess.run(
        [
            ffprobe,
            "-v", "error",
            "-select_streams", "v",
            "-show_entries", "stream=index,codec_name:stream_disposition=attached_pic",
            "-of", "json",
            video_path,
        ],
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        raise RuntimeError(
            f"ffprobe failed for '{video_path}': {result.stderr.strip()}"
        )

    try:
        data = json.loads(result.stdout)
    except json.JSONDecodeError as exc:
        raise RuntimeError(f"Could not parse ffprobe output: {exc}") from exc

    stream_index = None
    for stream in data.get("streams", []):
        disposition = stream.get("disposition", {})
        if disposition.get("attached_pic") == 1:
            stream_index = stream["index"]
            break

    if stream_index is None:
        # --- Fallback: AtomicParsley covr atom ---
        # Try to extract artwork embedded in the MP4 covr atom.
        # --extractPix writes files named <basename>_artwork_1.jpg next to the video.
        video_dir = os.path.dirname(os.path.abspath(video_path))
        video_base = os.path.splitext(os.path.basename(video_path))[0]
        try:
            ap_result = subprocess.run(
                [atomicparsley, video_path, "--extractPix"],
                capture_output=True,
                text=True,
                cwd=video_dir,
            )
        except FileNotFoundError as exc:
            raise RuntimeError(
                f"No attached-picture stream found in '{video_path}' and "
                f"AtomicParsley is not available for covr-atom fallback."
            ) from exc

        # Find the extracted file (AtomicParsley names it <base>_artwork_1.*)
        extracted = None
        for candidate in os.listdir(video_dir):
            if candidate.startswith(f"{video_base}_artwork") and not candidate == os.path.basename(video_path):
                extracted = os.path.join(video_dir, candidate)
                break

        if not extracted or not os.path.exists(extracted):
            raise RuntimeError(
                f"No embedded cover art found in '{video_path}' "
                f"(neither attached-picture stream nor covr atom)."
            )

        # Move the extracted file to the desired output_path
        shutil.move(extracted, output_path)
        return os.path.abspath(output_path)

    try:
        subprocess.run(
            [
                ffmpeg,
                "-y",
                "-i", video_path,
                "-map", f"0:{stream_index}",
                "-an",
                "-vcodec", "copy",
                "-f", "image2",
                output_path,
            ],
            capture_output=True,
            check=True,
        )
    except subprocess.CalledProcessError as exc:
        raise RuntimeError(
            f"ffmpeg failed to extract embedded image from '{video_path}': "
            f"{exc.stderr.decode(errors='replace').strip()}"
        ) from exc
    return os.path.abspath(output_path)
