"""
Utilities for managing image overlay assets, including SVG rasterization.
"""

from __future__ import annotations

import hashlib
import math
import os
import sys
import tempfile
from dataclasses import dataclass


class SVGConversionError(RuntimeError):
    """Raised when an SVG overlay cannot be rasterized."""
    pass


@dataclass(frozen=True)
class RasterizationResult:
    """Metadata about a rasterized overlay image."""

    path: str
    applied_scale: float


def _get_bpy_module():
    """Return bpy module if available, otherwise None."""
    return sys.modules.get("bpy")


def is_svg_path(path: str) -> bool:
    """Return True if the provided path looks like an SVG file."""
    if not path:
        return False
    return str(path).lower().endswith(".svg")


def get_overlay_cache_dir() -> str:
    """
    Return a writable directory for cached overlay images.

    Prefers Blender's CONFIG directory when available, otherwise falls back
    to the system temporary directory.
    """
    base_dir = None
    bpy_module = _get_bpy_module()

    if bpy_module is not None:
        bpy_utils = getattr(bpy_module, "utils", None)
        user_resource = getattr(bpy_utils, "user_resource", None)
        if callable(user_resource):
            try:
                base_dir = user_resource('CONFIG')
            except Exception:
                base_dir = None

        if not base_dir:
            bpy_app = getattr(bpy_module, "app", None)
            base_dir = getattr(bpy_app, "tempdir", None)

    if not base_dir:
        base_dir = tempfile.gettempdir()

    cache_dir = os.path.join(os.path.abspath(base_dir), "text_texture_generator", "overlay_cache")
    try:
        os.makedirs(cache_dir, exist_ok=True)
    except OSError:
        # Fall back to a temporary directory specific to this process.
        fallback_dir = os.path.join(tempfile.gettempdir(), "text_texture_generator", "overlay_cache")
        os.makedirs(fallback_dir, exist_ok=True)
        cache_dir = fallback_dir

    return cache_dir


def ensure_svg_raster(image_path: str, scale: float) -> RasterizationResult:
    """
    Ensure a rasterized PNG exists for the given SVG and return its path with applied scale.

    The rasterization scale is applied during conversion so the caller should not
    reapply the same scale.
    """
    if not image_path or not os.path.exists(image_path):
        raise SVGConversionError(f"SVG overlay path does not exist: {image_path}")

    try:
        import cairosvg  # type: ignore
    except ImportError:
        try:
            from .system import install_cairosvg_from_bundle
            install_cairosvg_from_bundle()
            import cairosvg  # type: ignore  # noqa: F401
        except Exception as install_error:
            raise SVGConversionError(
                "SVG overlay support requires CairoSVG, but the bundled installation failed. "
                "Please reinstall the addon or install CairoSVG manually inside Blender's Python environment."
            ) from install_error

    # Guard against invalid scales (NaN, infinity, zero, negative)
    if not isinstance(scale, (int, float)) or not math.isfinite(scale):
        raise SVGConversionError(f"Invalid overlay scale for SVG: {scale!r}")

    if scale <= 0:
        raise SVGConversionError("Overlay scale must be greater than zero for SVG rasterization.")

    normalized_scale = max(float(scale), 0.01)
    source_mtime = os.path.getmtime(image_path)
    cache_dir = get_overlay_cache_dir()

    cache_key = f"{os.path.abspath(image_path)}|{source_mtime:.6f}|{normalized_scale:.6f}"
    cache_name = hashlib.sha256(cache_key.encode("utf-8")).hexdigest()[:32] + ".png"
    cache_path = os.path.join(cache_dir, cache_name)

    if not os.path.exists(cache_path):
        # Perform rasterization and write to cache path
        with open(cache_path, "wb") as cache_file:
            cairosvg.svg2png(url=image_path, scale=normalized_scale, write_to=cache_file)  # type: ignore[arg-type]

    return RasterizationResult(path=cache_path, applied_scale=1.0)
