"""Cortex download + extract module.

Fetches the latest Cortex release from GitHub and extracts it to .ievo/cortex/.

Usage:
    tag, asset_url = asyncio.run(fetch_latest_cortex_tag())
    asyncio.run(download_cortex_release(tag, asset_url, root=Path.cwd()))

# TODO(IDEA-future): offline cortex cache — download once, reuse until explicit update
"""

from __future__ import annotations

import io
import os
import shutil
import tarfile
import tempfile
from pathlib import Path

import httpx

CORTEX_REPO = "ievo-ai/cortex"
CORTEX_API_URL = f"https://api.github.com/repos/{CORTEX_REPO}/releases/latest"
CORTEX_RELEASES_URL = f"https://github.com/{CORTEX_REPO}/releases"
CORTEX_DIR = ".ievo/cortex"
CORTEX_VERSION_FILE = ".ievo/cortex-version"


class CortexDownloadError(Exception):
    """Raised when the Cortex release cannot be fetched or extracted."""


async def fetch_latest_cortex_tag() -> tuple[str, str]:
    """Return (tag, asset_download_url) for the latest Cortex release.

    Raises CortexDownloadError on any failure (network, no assets, etc.).
    """
    headers: dict[str, str] = {}
    token = os.environ.get("GITHUB_TOKEN")
    if token:
        headers["Authorization"] = f"token {token}"

    try:
        async with httpx.AsyncClient(timeout=15) as client:
            resp = await client.get(CORTEX_API_URL, headers=headers)
            resp.raise_for_status()
            data = resp.json()
    except httpx.HTTPError as exc:
        raise CortexDownloadError(
            f"Failed to reach Cortex releases at {CORTEX_RELEASES_URL}: {exc}"
        ) from exc

    assets = data.get("assets", [])
    if not assets:
        raise CortexDownloadError(
            f"No release assets found for Cortex. Check {CORTEX_RELEASES_URL}"
        )

    tag = data["tag_name"]
    download_url = assets[0]["browser_download_url"]
    return tag, download_url


async def download_cortex_release(tag: str, asset_url: str, root: Path) -> None:
    """Download Cortex .tar.gz, extract to root/.ievo/cortex/, write version file.

    Idempotent: clears the cortex directory before extraction.
    Safe: uses tarfile filter='data' to block path traversal (Python 3.12+).
    """
    try:
        async with httpx.AsyncClient(timeout=60, follow_redirects=True) as client:
            resp = await client.get(asset_url)
            resp.raise_for_status()
            tar_bytes = resp.content
    except httpx.HTTPError as exc:
        raise CortexDownloadError(
            f"Failed to download Cortex release asset from {asset_url}: {exc}"
        ) from exc

    cortex_dir = root / CORTEX_DIR

    # Extract to a temp dir first, then atomically replace the cortex dir
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        try:
            with tarfile.open(fileobj=_bytes_to_fileobj(tar_bytes), mode="r:gz") as tf:
                tf.extractall(path=tmp_path, filter="data")
        except Exception as exc:
            raise CortexDownloadError(f"Failed to extract Cortex release asset: {exc}") from exc

        # Replace cortex dir with freshly extracted content
        if cortex_dir.exists():
            shutil.rmtree(cortex_dir)
        shutil.copytree(tmp_path, cortex_dir)

    # Write version file
    version_file = root / CORTEX_VERSION_FILE
    version_file.parent.mkdir(parents=True, exist_ok=True)
    version_file.write_text(tag + "\n")


def get_installed_cortex_version(root: Path) -> str | None:
    """Read .ievo/cortex-version and return the version string, or None if absent."""
    version_file = root / CORTEX_VERSION_FILE
    if not version_file.exists():
        return None
    return version_file.read_text().strip()


def _bytes_to_fileobj(data: bytes) -> io.BytesIO:
    """Wrap bytes in a file-like object for tarfile.open()."""
    return io.BytesIO(data)
