"""Desktop icon helpers for App Mode window and shell integration."""

from __future__ import annotations

from dataclasses import dataclass
import logging
import os
from pathlib import Path
import platform
import shutil
import struct
import subprocess
import sys
import tempfile

LOGGER = logging.getLogger("sentineledge.app_icon")


@dataclass(frozen=True)
class AppIconAssets:
    """Resolved icon assets for current platform."""

    source_png: Path | None
    window_icon: Path | None
    start_icon: Path | None


def _app_root() -> Path:
    """Resolve runtime root for source and frozen builds."""
    if getattr(sys, "frozen", False):  # pragma: no cover - packaging runtime path
        base = getattr(sys, "_MEIPASS", None)
        if base:
            return Path(str(base))
        return Path(sys.executable).resolve().parent
    return Path(__file__).resolve().parents[2]


def _favicon_path() -> Path:
    """Return path to canonical favicon PNG."""
    return _app_root() / "static" / "favicon.png"


def _icon_cache_dir() -> Path:
    """Return preferred icon cache directory."""
    raw = os.getenv("ORION_ICON_CACHE_DIR", "").strip()
    if raw:
        return Path(raw).expanduser().resolve()
    return Path.home() / ".orion-ai" / "cache" / "icons"


def _resolve_writable_icon_cache_dir() -> Path:
    """Resolve a writable icon cache directory with safe fallbacks."""
    preferred = _icon_cache_dir()
    candidates = [
        preferred,
        _app_root() / ".orion-ai-cache" / "icons",
        Path(tempfile.gettempdir()) / "orion-ai" / "icons",
    ]
    for candidate in candidates:
        try:
            candidate.mkdir(parents=True, exist_ok=True)
            probe = candidate / ".write_probe"
            probe.write_bytes(b"ok")
            probe.unlink(missing_ok=True)
            return candidate
        except Exception:
            continue
    # Last-resort path; write attempts may still fail and be handled upstream.
    return candidates[-1]


def _png_dimensions(path: Path) -> tuple[int, int]:
    """Parse PNG width/height from IHDR chunk."""
    data = path.read_bytes()
    if len(data) < 24 or data[:8] != b"\x89PNG\r\n\x1a\n":
        raise ValueError("Invalid PNG file")
    width, height = struct.unpack(">II", data[16:24])
    return int(width), int(height)


def _ensure_ico_from_png(source_png: Path, target_ico: Path) -> Path:
    """Create a standards-compliant ICO wrapping PNG payload."""
    png_bytes = source_png.read_bytes()
    width, height = _png_dimensions(source_png)
    icon_w = width if 0 < width < 256 else 0
    icon_h = height if 0 < height < 256 else 0
    header = struct.pack("<HHH", 0, 1, 1)
    entry = struct.pack(
        "<BBBBHHII",
        icon_w,
        icon_h,
        0,
        0,
        1,
        32,
        len(png_bytes),
        6 + 16,
    )
    target_ico.parent.mkdir(parents=True, exist_ok=True)
    target_ico.write_bytes(header + entry + png_bytes)
    return target_ico


def _run_checked(cmd: list[str]) -> bool:
    """Run one command and return True on success."""
    try:
        subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return True
    except Exception:
        return False


def _ensure_icns_from_png(source_png: Path, target_icns: Path) -> Path | None:
    """Create macOS .icns from PNG using sips/iconutil when available."""
    if shutil.which("iconutil") is None or shutil.which("sips") is None:
        return None

    iconset_dir = target_icns.parent / "orion-ai.iconset"
    if iconset_dir.exists():
        shutil.rmtree(iconset_dir, ignore_errors=True)
    iconset_dir.mkdir(parents=True, exist_ok=True)

    renditions = (
        ("icon_16x16.png", 16),
        ("icon_16x16@2x.png", 32),
        ("icon_32x32.png", 32),
        ("icon_32x32@2x.png", 64),
        ("icon_128x128.png", 128),
        ("icon_128x128@2x.png", 256),
        ("icon_256x256.png", 256),
        ("icon_256x256@2x.png", 512),
        ("icon_512x512.png", 512),
        ("icon_512x512@2x.png", 1024),
    )
    for filename, px in renditions:
        out_path = iconset_dir / filename
        if not _run_checked(["sips", "-z", str(px), str(px), str(source_png), "--out", str(out_path)]):
            return None

    target_icns.parent.mkdir(parents=True, exist_ok=True)
    if not _run_checked(["iconutil", "-c", "icns", str(iconset_dir), "-o", str(target_icns)]):
        return None
    return target_icns


def prepare_app_icon_assets() -> AppIconAssets:
    """Resolve/generate icon assets suitable for current desktop platform."""
    source_png = _favicon_path()
    if not source_png.exists():
        LOGGER.warning("App icon source not found: %s", source_png)
        return AppIconAssets(source_png=None, window_icon=None, start_icon=None)

    os_name = platform.system().lower()
    cache_dir = _resolve_writable_icon_cache_dir()

    if os_name == "windows":
        ico_path = cache_dir / "orion-ai.ico"
        try:
            _ensure_ico_from_png(source_png, ico_path)
        except Exception as exc:
            LOGGER.warning("Could not generate .ico icon: %s", exc)
            return AppIconAssets(source_png=source_png, window_icon=source_png, start_icon=source_png)
        return AppIconAssets(source_png=source_png, window_icon=ico_path, start_icon=ico_path)

    if os_name == "darwin":
        icns_path = cache_dir / "orion-ai.icns"
        generated = _ensure_icns_from_png(source_png, icns_path)
        if generated and generated.exists():
            return AppIconAssets(source_png=source_png, window_icon=generated, start_icon=generated)
        return AppIconAssets(source_png=source_png, window_icon=source_png, start_icon=source_png)

    return AppIconAssets(source_png=source_png, window_icon=source_png, start_icon=source_png)


def apply_platform_app_icon(source_png: Path | None) -> bool:
    """Apply dock/taskbar app icon for platforms that support runtime overrides."""
    if source_png is None or not source_png.exists():
        return False
    if platform.system().lower() != "darwin":
        return False

    try:  # pragma: no cover - runtime AppKit availability
        from AppKit import NSApplication, NSImage
    except Exception:
        return False

    try:  # pragma: no cover - runtime AppKit path
        app = NSApplication.sharedApplication()
        image = NSImage.alloc().initWithContentsOfFile_(str(source_png))
        if image is None:
            return False
        app.setApplicationIconImage_(image)
        return True
    except Exception:
        return False
