"""Extract embedded album art from media files using mutagen.

Ported from iOpenPod ArtworkDB_Writer/art_extractor.py.
Supports: MP3, M4A/AAC, M4B, M4V/MP4, FLAC, OGG, OPUS, AIFF/WAV.
"""

from __future__ import annotations

import os
from pathlib import Path

import mutagen


def extract_cover(file_path: str) -> bytes | None:
    """Extract the first embedded album art image from a media file.

    Returns raw image bytes (JPEG/PNG) or None.
    """
    path = Path(file_path)
    ext = path.suffix.lower()

    try:
        if ext in _IMAGE_EXTS:
            return path.read_bytes()
        if ext == ".mp3":
            return _extract_mp3(file_path)
        elif ext in (".m4a", ".m4p", ".m4b", ".aac", ".alac"):
            return _extract_mp4(file_path)
        elif ext in (".m4v", ".mp4", ".mov"):
            art = _extract_mp4(file_path)
            if art:
                return art
            return None
        elif ext == ".flac":
            return _extract_flac(file_path)
        elif ext == ".ogg":
            return _extract_ogg(file_path)
        elif ext == ".opus":
            return _extract_opus(file_path)
        elif ext in (".aif", ".aiff"):
            return _extract_aiff(file_path)
        else:
            return _extract_generic(file_path)
    except Exception:
        return None


def find_folder_art(file_path: str) -> str | None:
    """Return path of folder artwork image next to *file_path*, or None."""
    directory = str(Path(file_path).parent)
    try:
        entries = os.listdir(directory)
    except OSError:
        return None
    lower_map = {e.lower(): e for e in entries}
    for stem in _FOLDER_ART_NAMES:
        for ext in _IMAGE_EXTS:
            if (stem + ext) in lower_map:
                return os.path.join(directory, lower_map[stem + ext])
    return None


def extract_cover_with_fallback(file_path: str) -> bytes | None:
    """Extract cover art, falling back to folder.jpg if no embedded art."""
    art = extract_cover(file_path)
    if art is not None:
        return art
    folder_img = find_folder_art(file_path)
    if folder_img:
        try:
            return Path(folder_img).read_bytes()
        except OSError:
            pass
    return None


_FOLDER_ART_NAMES = ("cover", "folder", "album", "front", "artwork", "thumb")
_IMAGE_EXTS = (".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp")


def _extract_mp3(path: str) -> bytes | None:
    from mutagen.mp3 import MP3
    audio = MP3(path)
    if audio.tags is None:
        return None
    for key in audio.tags:
        if key.startswith("APIC"):
            return audio.tags[key].data
    return None


def _extract_mp4(path: str) -> bytes | None:
    from mutagen.mp4 import MP4
    audio = MP4(path)
    if audio.tags is None:
        return None
    covers = audio.tags.get("covr", [])
    if covers:
        return bytes(covers[0])
    return None


def _extract_flac(path: str) -> bytes | None:
    from mutagen.flac import FLAC
    audio = FLAC(path)
    if audio.pictures:
        return audio.pictures[0].data
    return None


def _extract_ogg(path: str) -> bytes | None:
    from mutagen.oggvorbis import OggVorbis
    audio = OggVorbis(path)
    return _extract_vorbis_picture(audio)


def _extract_opus(path: str) -> bytes | None:
    from mutagen.oggopus import OggOpus
    audio = OggOpus(path)
    return _extract_vorbis_picture(audio)


def _extract_vorbis_picture(audio) -> bytes | None:
    import base64
    pictures = audio.get("metadata_block_picture", [])
    if pictures:
        try:
            from mutagen.flac import Picture
            pic = Picture(base64.b64decode(pictures[0]))
            return pic.data
        except Exception:
            pass
    return None


def _extract_aiff(path: str) -> bytes | None:
    from mutagen.aiff import AIFF
    audio = AIFF(path)
    if audio.tags is None:
        return None
    for key in audio.tags:
        if key.startswith("APIC"):
            return audio.tags[key].data
    return None


def _extract_generic(path: str) -> bytes | None:
    audio = mutagen.File(path)
    if audio is None or audio.tags is None:
        return None
    for key in audio.tags:
        if hasattr(key, "startswith") and key.startswith("APIC"):
            frame = audio.tags[key]
            if hasattr(frame, "data"):
                return frame.data
    covers = audio.tags.get("covr", [])
    if covers:
        return bytes(covers[0])
    return None
