"""Media handling utilities for Telegram channel."""

from __future__ import annotations

import io
import mimetypes
from enum import Enum
from pathlib import Path

import httpx
from loguru import logger
from PIL import Image

# Telegram API photo size limit (6MB)
TELEGRAM_PHOTO_SIZE_LIMIT = 6_000_000

try:
    import magic
    HAS_MAGIC = True
except ImportError:
    HAS_MAGIC = False

try:
    from pillow_heif import register_heif_opener
    register_heif_opener()
    HAS_HEIF = True
except ImportError:
    HAS_HEIF = False


class MediaKind(Enum):
    """Media type classification."""
    IMAGE = "image"
    VIDEO = "video"
    AUDIO = "audio"
    DOCUMENT = "document"


def detect_mime(path: str, content: bytes | None = None) -> str:
    """
    Detect MIME type of media file.

    Priority:
    1. python-magic sniff (if available and content provided)
    2. Extension-based lookup
    3. Fallback to application/octet-stream

    Args:
        path: File path (used for extension detection)
        content: Optional file content bytes for magic sniffing

    Returns:
        MIME type string (e.g., "image/jpeg")
    """
    # Try magic detection first if we have content
    if HAS_MAGIC and content:
        try:
            mime = magic.from_buffer(content, mime=True)
            # Avoid generic types if we can be more specific from extension
            if mime and mime != "application/octet-stream":
                return mime
        except Exception as e:
            logger.debug(f"Magic detection failed, falling back to extension: {e}")

    # Extension-based detection
    mime_type, _ = mimetypes.guess_type(path)
    if mime_type:
        return mime_type

    # Fallback
    return "application/octet-stream"


def classify_media(mime: str) -> MediaKind:
    """
    Classify MIME type into media kind.

    Args:
        mime: MIME type string (e.g., "image/jpeg")

    Returns:
        MediaKind enum value
    """
    if mime.startswith("image/"):
        return MediaKind.IMAGE
    if mime.startswith("video/"):
        return MediaKind.VIDEO
    if mime.startswith("audio/"):
        return MediaKind.AUDIO
    # Everything else is a document
    return MediaKind.DOCUMENT


def is_heic_format(path: str) -> bool:
    """
    Check if file is HEIC/HEIF format.

    Args:
        path: File path

    Returns:
        True if file extension is .heic or .heif
    """
    ext = Path(path).suffix.lower()
    return ext in (".heic", ".heif")


async def fetch_media(url: str, max_bytes: int) -> tuple[bytes, str]:
    """
    Download media from remote URL.

    Args:
        url: Remote URL to fetch
        max_bytes: Maximum size to download

    Returns:
        Tuple of (content bytes, detected MIME type)

    Raises:
        ValueError: If download fails or exceeds size limit
    """
    try:
        async with httpx.AsyncClient(timeout=10.0) as client:
            response = await client.get(url, follow_redirects=True)
            response.raise_for_status()

            content = response.content

            if len(content) > max_bytes:
                raise ValueError(f"Media exceeds size limit: {len(content)} > {max_bytes}")

            # Get MIME type from response or detect
            mime = response.headers.get("content-type", "application/octet-stream")
            # Strip charset if present (e.g., "image/jpeg; charset=utf-8" → "image/jpeg")
            mime = mime.split(";")[0].strip()

            # Detect from content if generic type
            if mime == "application/octet-stream":
                mime = detect_mime(url, content)

            return content, mime

    except httpx.TimeoutException as e:
        raise ValueError(f"Download timeout: {url}") from e
    except httpx.HTTPError as e:
        raise ValueError(f"Download failed: {url}: {e}") from e


def optimize_image(path: str, max_bytes: int = TELEGRAM_PHOTO_SIZE_LIMIT) -> bytes:
    """
    Optimize image to fit under size limit.

    Strategy:
    1. Convert HEIC to JPEG if needed
    2. PNG with alpha → preserve with compression levels [6,7,8,9]
    3. JPEG/PNG without alpha → resize + quality grid

    Sizes: [2048, 1536, 1280, 1024, 800] px (max dimension)
    Qualities: [80, 70, 60, 50, 40] (JPEG only)

    Args:
        path: Path to image file
        max_bytes: Maximum size in bytes (default 6MB for Telegram)

    Returns:
        Optimized image bytes

    Raises:
        ValueError: If image cannot be optimized under limit
    """
    # Load image with context manager to ensure file handle is closed
    with Image.open(path) as img:
        # Convert HEIC to JPEG
        if is_heic_format(path):
            if not HAS_HEIF:
                raise ValueError("pillow-heif not available for HEIC conversion")
            # Convert to RGB (HEIC → JPEG)
            if img.mode != "RGB":
                img = img.convert("RGB")
            return _optimize_jpeg(img, max_bytes)

        # PNG with alpha channel - preserve it
        if img.mode == "RGBA" or img.mode == "LA":
            return _optimize_png(img, max_bytes)

        # Everything else → convert to JPEG and optimize
        if img.mode != "RGB":
            img = img.convert("RGB")
        return _optimize_jpeg(img, max_bytes)


def _optimize_jpeg(img: Image.Image, max_bytes: int) -> bytes:
    """Optimize JPEG with size/quality grid."""
    sizes = [2048, 1536, 1280, 1024, 800]
    qualities = [80, 70, 60, 50, 40]

    for size in sizes:
        # Always copy to avoid mutation issues
        resized = img.copy()
        if max(img.size) > size:
            resized.thumbnail((size, size), Image.Resampling.LANCZOS)

        for quality in qualities:
            buf = io.BytesIO()
            resized.save(buf, format="JPEG", quality=quality, optimize=True)
            data = buf.getvalue()

            if len(data) <= max_bytes:
                return data

    # If we get here, even smallest size/quality is too large
    raise ValueError(f"Cannot optimize image under {max_bytes} bytes")


def _optimize_png(img: Image.Image, max_bytes: int) -> bytes:
    """Optimize PNG while preserving alpha channel."""
    compress_levels = [6, 7, 8, 9]
    sizes = [2048, 1536, 1280, 1024, 800]

    for size in sizes:
        # Always copy to avoid mutation issues
        resized = img.copy()
        if max(img.size) > size:
            resized.thumbnail((size, size), Image.Resampling.LANCZOS)

        for compress_level in compress_levels:
            buf = io.BytesIO()
            resized.save(buf, format="PNG", compress_level=compress_level, optimize=True)
            data = buf.getvalue()

            if len(data) <= max_bytes:
                return data

    # Fallback: try converting to JPEG if still too large
    if img.mode in ("RGBA", "LA"):
        # Create white background
        background = Image.new("RGB", img.size, (255, 255, 255))
        if img.mode == "RGBA":
            background.paste(img, mask=img.split()[3])  # Use alpha as mask
        else:  # LA (grayscale + alpha)
            background.paste(img.convert("L"), mask=img.split()[1])
        return _optimize_jpeg(background, max_bytes)

    raise ValueError(f"Cannot optimize PNG under {max_bytes} bytes")


def group_media_for_album(media_items: list[tuple[str, MediaKind]]) -> dict[str, list[str]]:
    """
    Group media items for album sending.

    Logic:
    - All images (2+) → album
    - All videos (2+) → album
    - Mixed types → separate
    - Single item → separate

    Args:
        media_items: List of (path, MediaKind) tuples

    Returns:
        Dict with 'album' and 'separate' keys containing lists of paths
    """
    if len(media_items) <= 1:
        return {
            "album": [],
            "separate": [path for path, _ in media_items]
        }

    # Count each kind
    kinds = [kind for _, kind in media_items]
    unique_kinds = set(kinds)

    # All same type → album (if images or videos)
    if len(unique_kinds) == 1:
        kind = kinds[0]
        if kind in (MediaKind.IMAGE, MediaKind.VIDEO):
            return {
                "album": [path for path, _ in media_items],
                "separate": []
            }

    # Mixed types or non-album-able types → separate
    return {
        "album": [],
        "separate": [path for path, _ in media_items]
    }
