#!/usr/bin/env python3
"""Reusable helpers for Skills that call the LeapSkills Runtime API."""

from __future__ import annotations

import base64
import json
import os
import sys
from pathlib import Path
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.parse import urljoin, urlparse
from urllib.request import Request, urlopen

DEFAULT_LEAPSKILLS_BASE_URL = "https://leapskills.leapinfra.cn"
IMAGE_GENERATIONS_PATH = "/v1/images/generations"

class LeapSkillsRuntimeError(RuntimeError):
    """Raised when a Runtime API request cannot be completed."""

def env_value(name: str, default: str = "") -> str:
    return os.environ.get(name, default).strip()

def default_token_path() -> Path:
    override = env_value("LEAPALL_OIDC_TOKEN_PATH")
    if override:
        return Path(override)
    if os.name == "nt":
        root = env_value("LOCALAPPDATA") or env_value("APPDATA") or str(Path.home())
        return Path(root) / "LeapSkills" / "leapall_oidc_access_token.txt"
    if sys.platform == "darwin":
        return Path.home() / "Library" / "Caches" / "LeapSkills" / "leapall_oidc_access_token.txt"
    root = env_value("XDG_CACHE_HOME")
    if root:
        return Path(root) / "leapskills" / "leapall_oidc_access_token.txt"
    return Path.home() / ".cache" / "leapskills" / "leapall_oidc_access_token.txt"

def resolve_leapskills_origin(value: str = "") -> str:
    raw = (
        value.strip()
        or env_value("LEAPSKILLS_BASE_URL")
        or env_value("LEAPSKILLS_RUNTIME_BASE_URL")
        or DEFAULT_LEAPSKILLS_BASE_URL
    )
    return raw.rstrip("/")

def runtime_url(path: str, runtime_origin: str = "") -> str:
    origin = resolve_leapskills_origin(runtime_origin)
    return urljoin(origin + "/", path.lstrip("/"))

def read_oidc_access_token() -> str:
    token = env_value("LEAPALL_OIDC_ACCESS_TOKEN")
    if token:
        return token

    path = default_token_path()
    try:
        token = path.read_text(encoding="utf-8").strip()
    except OSError:
        token = ""
    if token:
        return token

    raise LeapSkillsRuntimeError("Missing LeapAll OIDC access token. Run the Skill OIDC helper, then retry.")

def parse_runtime_error(status: int, detail: str) -> str:
    try:
        payload = json.loads(detail)
        code = payload.get("error", {}).get("code") or payload.get("code")
    except json.JSONDecodeError:
        code = None

    if status == 401:
        return "LeapSkills Runtime returned 401. Sign in with LeapAll and retry."
    if status == 403 and code == "forbidden":
        return "LeapSkills Runtime returned 403. This account cannot call the requested Runtime capability."
    if status == 403 and code in {"skill_not_enabled", "model_not_allowed"}:
        return "LeapSkills Runtime returned 403. This Skill or model is not available for the current user."
    if status == 404 and code == "model_not_configured":
        return "LeapSkills Runtime returned 404. The platform model is not configured."
    if status == 429:
        return "LeapSkills Runtime returned 429. The request is rate limited."
    if status == 502:
        return "LeapSkills Runtime returned 502. The upstream model service is temporarily unavailable."
    return f"LeapSkills Runtime returned HTTP {status}: {detail}"

def post_json(path: str, payload: dict[str, Any], skill_slug: str, token: str | None = None, runtime_origin: str = "", timeout: int = 180) -> dict[str, Any]:
    endpoint = runtime_url(path, runtime_origin)
    body = json.dumps(payload).encode("utf-8")
    request = Request(
        endpoint,
        data=body,
        headers={
            "Authorization": f"Bearer {token or read_oidc_access_token()}",
            "Content-Type": "application/json",
            "Accept": "application/json",
            "X-LeapSkills-Skill-Slug": skill_slug,
        },
        method="POST",
    )
    try:
        with urlopen(request, timeout=timeout) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")
        raise LeapSkillsRuntimeError(parse_runtime_error(exc.code, detail)) from exc
    except URLError as exc:
        raise LeapSkillsRuntimeError(f"LeapSkills Runtime request failed: {exc}") from exc
    except json.JSONDecodeError as exc:
        raise LeapSkillsRuntimeError(f"LeapSkills Runtime returned non-JSON response: {exc}") from exc

def call_image_generation(
    *,
    skill_slug: str,
    model: str,
    prompt: str,
    size: str = "1024x1024",
    response_format: str = "url",
    runtime_origin: str = "",
    timeout: int = 180,
) -> dict[str, Any]:
    payload = {
        "model": model,
        "prompt": prompt,
        "size": size,
        "n": 1,
        "response_format": response_format,
    }
    return post_json(IMAGE_GENERATIONS_PATH, payload, skill_slug, runtime_origin=runtime_origin, timeout=timeout)

def download_url(url: str, timeout: int = 180) -> bytes:
    try:
        with urlopen(url, timeout=timeout) as response:
            return response.read()
    except HTTPError as exc:
        raise LeapSkillsRuntimeError(f"Generated image download returned HTTP {exc.code}: {exc.reason}") from exc
    except URLError as exc:
        raise LeapSkillsRuntimeError(f"Generated image download failed: {exc}") from exc

def extension_from_url(url: str) -> str:
    suffix = Path(urlparse(url).path).suffix.lower()
    return suffix if suffix in {".png", ".jpg", ".jpeg", ".webp"} else ".png"

def output_path(base_out: Path, index: int, count: int, ext: str) -> Path:
    if count == 1:
        return base_out
    suffix = base_out.suffix or ext
    return base_out.with_name(f"{base_out.stem}-{index + 1}{suffix}")

def write_images(response: dict[str, Any], out: Path, timeout: int = 180) -> list[str]:
    data = response.get("data")
    if not isinstance(data, list) or not data:
        raise LeapSkillsRuntimeError("LeapSkills Runtime response did not include image data.")

    out.parent.mkdir(parents=True, exist_ok=True)
    paths: list[str] = []

    for index, item in enumerate(data):
        if not isinstance(item, dict):
            raise LeapSkillsRuntimeError("LeapSkills Runtime returned a non-object image item.")
        if isinstance(item.get("b64_json"), str):
            raw = base64.b64decode(item["b64_json"])
            path = output_path(out, index, len(data), out.suffix or ".png")
        elif isinstance(item.get("url"), str):
            raw = download_url(item["url"], timeout)
            path = output_path(out, index, len(data), out.suffix or extension_from_url(item["url"]))
        else:
            raise LeapSkillsRuntimeError("LeapSkills Runtime image item has neither url nor b64_json.")
        path.write_bytes(raw)
        paths.append(str(path.resolve()))

    return paths
