"""
Module: registry_command
Description: Registry operations (publish, install, list, run)

Implements:
    - docs/cli/reference.md#huitzo-publish
    - docs/cli/reference.md#huitzo-install
    - docs/cli/reference.md#huitzo-list
    - docs/cli/reference.md#huitzo-run
"""

import json
import subprocess
from pathlib import Path
from typing import Any

import httpx
import typer
import yaml
from rich.console import Console

from huitzo_cli.auth import AuthClient
from huitzo_cli.config import ConfigManager
from huitzo_cli.http_utils import parse_error

console = Console()
registry_app = typer.Typer(help="Registry operations")


@registry_app.callback(invoke_without_command=True)
def registry_callback(ctx: typer.Context) -> None:
    """Registry operations - publish, install, list, and run packs."""
    if ctx.invoked_subcommand is None:
        console.print(ctx.get_help())
        raise typer.Exit(0)


def _get_auth() -> tuple[AuthClient, str, str]:
    """Get auth client, token, and API URL. Raises Exit if not authenticated."""
    config = ConfigManager()
    auth = AuthClient(config)
    token = auth.get_token()
    if not token:
        console.print("[red]Not authenticated. Run: huitzo login[/red]")
        raise typer.Exit(1)
    api_url = auth.get_api_url()
    return auth, token, api_url


def _load_pack_manifest() -> dict[str, Any]:
    """Load huitzo.yaml from current directory."""
    manifest_path = Path.cwd() / "huitzo.yaml"
    if not manifest_path.exists():
        console.print("[red]No huitzo.yaml found. Are you in a pack directory?[/red]")
        raise typer.Exit(1)
    with open(manifest_path) as f:
        data: dict[str, Any] = yaml.safe_load(f)
        return data


@registry_app.command()
def publish() -> None:
    """Publish pack to Huitzo backend.

    Builds a wheel (if needed), creates the pack on the backend,
    and uploads the wheel as a new version.
    """
    manifest = _load_pack_manifest()
    auth, token, api_url = _get_auth()

    pack_name = manifest.get("name", "")
    namespace = manifest.get("namespace", pack_name)
    version = manifest.get("version", "0.0.0")
    headers = {"Authorization": f"Bearer {token}"}

    # Find or build wheel
    dist_dir = Path.cwd() / "dist"
    wheels = list(dist_dir.glob("*.whl")) if dist_dir.exists() else []

    if not wheels:
        console.print("[bold]Building wheel...[/bold]")
        result = subprocess.run(
            ["huitzo", "build"],
            capture_output=True,
            text=True,
            check=False,
        )
        if result.returncode != 0:
            console.print(f"[red]Build failed: {result.stderr}[/red]")
            raise typer.Exit(1)
        wheels = list(dist_dir.glob("*.whl")) if dist_dir.exists() else []

    if not wheels:
        console.print("[red]No wheel found in dist/. Run: huitzo build[/red]")
        raise typer.Exit(1)

    wheel_path = sorted(wheels, key=lambda p: p.stat().st_mtime)[-1]

    # Create pack (409 = already exists, that's OK)
    # Send scope WITHOUT @ prefix — the backend adds it (packs.py:91)
    scope = namespace
    console.print(f"[bold]Registering pack @{scope}/{pack_name}...[/bold]")
    try:
        resp = httpx.post(
            f"{api_url}/api/v1/packs",
            json={"scope": scope, "name": pack_name, "visibility": "private"},
            headers=headers,
            timeout=30,
        )
    except httpx.HTTPError as e:
        console.print(f"[red]Could not connect to backend at {api_url}: {e}[/red]")
        raise typer.Exit(1)

    pack_id = None
    if resp.status_code == 201:
        console.print("[green]Pack created[/green]")
        pack_id = resp.json().get("data", {}).get("id")
    elif resp.status_code == 409:
        console.print("Pack already registered")
    else:
        console.print(f"[red]Failed to register pack: {parse_error(resp)}[/red]")
        raise typer.Exit(1)

    # If 409 (already exists), look up the pack_id by iterating owned packs.
    # Note: the backend list endpoint does not support query-param filtering and
    # excludes unlisted packs, so this fallback only works for non-unlisted packs.
    if not pack_id:
        # Backend stores scope with @ prefix; match against stored value
        stored_scope = f"@{scope}"
        try:
            resp = httpx.get(
                f"{api_url}/api/v1/packs",
                headers=headers,
                timeout=30,
            )
        except httpx.HTTPError as e:
            console.print(f"[red]Could not connect to backend at {api_url}: {e}[/red]")
            raise typer.Exit(1)

        if resp.status_code != 200:
            console.print("[red]Failed to list packs[/red]")
            raise typer.Exit(1)

        packs = resp.json().get("data", [])
        for p in packs:
            if p["scope"] == stored_scope and p["name"] == pack_name:
                pack_id = p["id"]
                break

    if not pack_id:
        console.print(
            "[red]Could not find pack after creation. "
            "If the pack has unlisted visibility, the list API cannot resolve it.[/red]"
        )
        raise typer.Exit(1)

    # Upload wheel
    console.print(f"[bold]Uploading {wheel_path.name} (v{version})...[/bold]")
    try:
        with open(wheel_path, "rb") as f:
            resp = httpx.post(
                f"{api_url}/api/v1/packs/{pack_id}/versions",
                files={"wheel": (wheel_path.name, f, "application/octet-stream")},
                data={"version": version},
                headers=headers,
                timeout=120,
            )
    except httpx.HTTPError as e:
        console.print(f"[red]Could not connect to backend at {api_url}: {e}[/red]")
        raise typer.Exit(1)

    if resp.status_code != 201:
        console.print(f"[red]Upload failed: {parse_error(resp)}[/red]")
        raise typer.Exit(1)

    data = resp.json().get("data", {})
    commands = data.get("commands", [])

    console.print(f"[green]Published v{version} successfully[/green]")
    if commands:
        console.print("[bold]Registered commands:[/bold]")
        for cmd in commands:
            console.print(f"  {cmd['namespace']}")


@registry_app.command()
def install(
    package: str = typer.Argument(..., help="Package name or path to .whl file"),
) -> None:
    """Install a pack from registry or local file.

    Args:
        package: Package name (from registry) or path to .whl file
    """
    # Check if it's a local file
    if package.endswith(".whl"):
        path = Path(package)
        if not path.exists():
            console.print(f"[red]File not found: {package}[/red]")
            raise typer.Exit(1)

        console.print(f"[bold]Installing from: {package}[/bold]")
        try:
            result = subprocess.run(["pip", "install", str(path)], check=False)
            if result.returncode != 0:
                raise typer.Exit(result.returncode)
            console.print("[green]Installation successful[/green]")
        except FileNotFoundError:
            console.print("[red]pip not found[/red]")
            raise typer.Exit(1)
    else:
        # Try to install from registry
        console.print(f"[bold]Installing: {package}[/bold]")
        console.print("[yellow]Registry install not yet available. Use a local .whl file.[/yellow]")
        raise typer.Exit(1)


@registry_app.command()
def list_packs() -> None:
    """List installed packs."""
    console.print("[bold]Installed Huitzo Packs:[/bold]")

    try:
        result = subprocess.run(
            ["pip", "list", "--format", "json"],
            capture_output=True,
            text=True,
            check=True,
        )
        packages = json.loads(result.stdout)
        huitzo_packs = [p for p in packages if "pack" in p["name"].lower()]

        if huitzo_packs:
            for pack in huitzo_packs:
                console.print(f"  {pack['name']} ({pack['version']})")
        else:
            console.print("  (none installed)")
    except (FileNotFoundError, json.JSONDecodeError):
        console.print("[yellow]Could not list packages[/yellow]")


@registry_app.command()
def run(
    pack_command: str = typer.Argument(..., help="Command namespace (e.g., @scope/pack/command)"),
    args_json: str = typer.Option("{}", "--args", help="JSON arguments"),
) -> None:
    """Run a pack command via the backend API.

    Args:
        pack_command: Full command namespace (@scope/pack/command) or pack:command shorthand
    """
    auth, token, api_url = _get_auth()
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
    }

    # Parse namespace
    if ":" in pack_command and not pack_command.startswith("@"):
        # Convert pack:command shorthand to @namespace/pack/command
        parts = pack_command.split(":", 1)
        pack_command = f"@{parts[0]}/{parts[1]}"

    try:
        body = json.loads(args_json)
    except json.JSONDecodeError:
        console.print("[red]Invalid JSON args[/red]")
        raise typer.Exit(1)

    console.print(f"[bold]Executing {pack_command}...[/bold]")

    try:
        resp = httpx.post(
            f"{api_url}/api/v1/commands/{pack_command}",
            json={"args": body},
            headers=headers,
            timeout=120,
        )
    except httpx.HTTPError as e:
        console.print(f"[red]Could not connect to backend at {api_url}: {e}[/red]")
        raise typer.Exit(1)

    if resp.status_code != 200:
        console.print(f"[red]Execution failed: {parse_error(resp)}[/red]")
        raise typer.Exit(1)

    data = resp.json().get("data", {})
    console.print(json.dumps(data, indent=2))
