"""Skill commands for manta-client CLI."""

from __future__ import annotations

import asyncio
import re
from typing import Any

import typer

from manta_client.api.agent.skill import get_skill_api
from manta_client.api.client import APIError
from manta_client.exitcodes import exit_code_for_api_error
from manta_client.output import (
    console,
    is_quiet,
    print_error,
    print_json,
    print_key_value,
    print_success,
    print_table,
)

app = typer.Typer(help="Skill management")

# Pattern: owner/repo/path/to/skill@tag
SOURCE_REF_PATTERN = re.compile(
    r"^(?P<owner>[^/]+)/(?P<repo>[^/]+)/(?P<path>.+?)(?:@(?P<tag>[^@]+))?$"
)


def _parse_source_ref(ref: str) -> tuple[str, str, str | None]:
    """Parse a source reference like 'owner/repo/path/to/skill@tag'.

    Returns:
        Tuple of (source_repo, source_path, tag)
    """
    m = SOURCE_REF_PATTERN.match(ref)
    if not m:
        raise typer.BadParameter(
            f"Invalid source reference: {ref}\n"
            "Expected format: owner/repo/path/to/skill[@tag]\n"
            "Example: zilliztech/manta/agent-skills/milvus-deploy@v1.0.0"
        )
    source_repo = f"{m.group('owner')}/{m.group('repo')}"
    source_path = m.group("path")
    tag = m.group("tag")
    return source_repo, source_path, tag


@app.command("list")
def list_skills(
    limit: int = typer.Option(20, "--limit", "-l", help="Max results"),
    category: str = typer.Option(None, "--category", "-c", help="Filter by category"),
    json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
) -> None:
    """List skills."""

    async def _list():
        api = get_skill_api()
        return await api.list_skills(limit=limit)

    try:
        result = asyncio.run(_list())
    except APIError as e:
        print_error(e.message)
        raise typer.Exit(exit_code_for_api_error(e.code)) from None

    items = result.get("items", [])
    total = result.get("total", 0)

    if json_output:
        print_json(result)
        return

    if not items:
        if not is_quiet():
            console.print("[dim]No skills found.[/dim]")
        return

    if is_quiet():
        for item in items:
            print(item.get("slug", ""))
        return

    # Mark built-in skills in the source column
    for item in items:
        if item.get("is_builtin"):
            item["source_display"] = "builtin"
        else:
            item["source_display"] = item.get("source_repo") or "-"

    columns = [
        ("id", "ID"),
        ("slug", "Slug"),
        ("name", "Name"),
        ("category", "Category"),
        ("version", "Version"),
        ("source_display", "Source"),
        ("status", "Status"),
    ]
    print_table(items, columns=columns, title=f"Skills ({total})")


@app.command("info")
def skill_info(
    slug: str = typer.Argument(..., help="Skill slug"),
    json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
) -> None:
    """Show skill details."""

    async def _get():
        api = get_skill_api()
        return await api.get_skill(slug)

    try:
        skill = asyncio.run(_get())
    except APIError as e:
        print_error(e.message)
        raise typer.Exit(exit_code_for_api_error(e.code)) from None

    if json_output:
        print_json(skill)
        return

    data: dict[str, Any] = {
        "ID": skill.get("id"),
        "Slug": skill.get("slug"),
        "Name": skill.get("name"),
        "Category": skill.get("category") or "-",
        "Agent": skill.get("agent") or "-",
        "Version": skill.get("version"),
        "Status": skill.get("status"),
        "Source Repo": skill.get("source_repo") or "-",
        "Source Path": skill.get("source_path") or "-",
        "Source Tag": skill.get("source_tag") or "-",
        "Archive": skill.get("archive_path") or "-",
    }
    print_key_value(data, title="Skill Details")


def _try_local_download(
    source_repo: str, source_path: str, tag: str | None
) -> list[tuple[bytes, str, str]] | None:
    """Try to download skill(s) locally via gh CLI.

    Supports both single skill and directory containing multiple skills.

    Returns list of (archive_bytes, skill_source_path, version) or None if gh unavailable.
    """
    from manta_client.ghcli import (
        GhCliError,
        check_gh_auth,
        check_gh_available,
        discover_skills_in_tarball,
        download_repo_tarball,
        extract_and_package_skill,
        get_commit_sha,
        resolve_ref,
    )

    if not check_gh_available() or not check_gh_auth():
        return None

    try:
        ref = tag or resolve_ref(source_repo)
        version = tag or get_commit_sha(source_repo, ref)
        if not is_quiet():
            console.print(f"[dim]Downloading {source_repo}@{ref} ({version})...[/dim]")
        tarball = download_repo_tarball(source_repo, ref)

        # Discover skills (single or multiple)
        skill_paths = discover_skills_in_tarball(tarball, source_path)
        if len(skill_paths) > 1 and not is_quiet():
            console.print(f"[dim]Discovered {len(skill_paths)} skills under {source_path}[/dim]")

        results: list[tuple[bytes, str, str]] = []
        for path in skill_paths:
            archive_bytes, _metadata = extract_and_package_skill(tarball, path)
            results.append((archive_bytes, path, version))
        return results
    except GhCliError as e:
        if not is_quiet():
            console.print(f"[dim]Local download failed ({e}), falling back to server...[/dim]")
        return None


@app.command("add")
def add_skill(
    source_ref: str = typer.Argument(..., help="Source reference: owner/repo/path/to/skill[@tag]"),
    json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
) -> None:
    """Add (sync) skill(s) from GitHub.

    Supports single skill or a directory containing multiple skills.
    Uses local gh CLI when available, falls back to server-side fetch.

    Examples:
        manta-client skill add zilliztech/manta/agent-skills/milvus-deploy
        manta-client skill add zilliztech/manta/agent-skills  # batch add all
    """
    source_repo, source_path, tag = _parse_source_ref(source_ref)
    local_skills = _try_local_download(source_repo, source_path, tag)

    try:

        async def _add():
            api = get_skill_api()
            if local_skills is not None:
                results = []
                for archive_bytes, skill_path, version in local_skills:
                    slug = skill_path.rsplit("/", 1)[-1]
                    if not is_quiet():
                        console.print(f"[dim]Uploading {slug}...[/dim]")
                    r = await api.upload_skill(archive_bytes, source_repo, skill_path, version)
                    results.append(r)
                return results
            # Fallback to server-side fetch (single skill only)
            data: dict[str, Any] = {"source_repo": source_repo, "source_path": source_path}
            if tag:
                data["tag"] = tag
            return [await api.create_skill(data)]

        results = asyncio.run(_add())
    except APIError as e:
        print_error(e.message)
        raise typer.Exit(exit_code_for_api_error(e.code)) from None

    if json_output:
        print_json(results if len(results) > 1 else results[0])
    elif is_quiet():
        for r in results:
            print(r.get("slug", ""))
    else:
        for r in results:
            slug = r.get("slug", "?")
            version = r.get("version", "?")
            print_success(f"Skill synced: {slug} @ {version}")


@app.command("update")
def update_skill(
    slug: str = typer.Argument(..., help="Skill slug"),
    tag: str = typer.Option(None, "--tag", "-t", help="Target git tag (default: latest)"),
    json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
) -> None:
    """Update a skill to a new version.

    Uses local gh CLI when available, falls back to server-side fetch.

    Example: manta-client skill update milvus-deploy --tag v2.0.0
    """
    try:

        async def _do_update():
            api = get_skill_api()
            skill = await api.get_skill(slug)

            source_repo = skill.get("source_repo")
            source_path = skill.get("source_path")

            # Try local-first if skill has GitHub source
            if source_repo and source_path:
                local = _try_local_download(source_repo, source_path, tag)
                if local is not None and len(local) == 1:
                    archive_bytes, skill_path, version = local[0]
                    if not is_quiet():
                        console.print("[dim]Uploading to server...[/dim]")
                    return await api.upload_skill(archive_bytes, source_repo, skill_path, version)

            # Fallback to server-side update
            data: dict[str, Any] = {}
            if tag:
                data["tag"] = tag
            return await api.update_skill(slug, data)

        result = asyncio.run(_do_update())
    except APIError as e:
        print_error(e.message)
        raise typer.Exit(exit_code_for_api_error(e.code)) from None

    if json_output:
        print_json(result)
    elif is_quiet():
        print(slug)
    else:
        version = result.get("version", "?")
        print_success(f"Skill updated: {slug} @ {version}")


@app.command("remove")
def remove_skill(
    slug: str = typer.Argument(..., help="Skill slug"),
) -> None:
    """Remove a skill.

    Example: manta-client skill remove milvus-deploy
    """

    async def _remove():
        api = get_skill_api()
        return await api.delete_skill(slug)

    try:
        asyncio.run(_remove())
        if is_quiet():
            print("deleted")
        else:
            print_success(f"Skill '{slug}' removed.")
    except APIError as e:
        print_error(e.message)
        raise typer.Exit(exit_code_for_api_error(e.code)) from None


@app.command("install-local")
def install_local(
    force: bool = typer.Option(False, "--force", "-f", help="Force overwrite even if up-to-date"),
    json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
) -> None:
    """Install/update the bundled Claude Code skill locally.

    Copies the SKILL.md shipped with this package to ~/.claude/skills/manta-client/.
    Run this after upgrading manta-client to keep the skill in sync.

    Example: manta-client skill install-local
    """
    from manta_client.local_skill import install_local_skill

    try:
        action, old_version, new_version = install_local_skill(force=force)
    except Exception as e:
        print_error(str(e))
        raise typer.Exit(1) from None

    if json_output:
        print_json({"action": action, "old_version": old_version, "new_version": new_version})
        return

    if action == "skipped":
        if is_quiet():
            print("skipped")
        else:
            console.print(f"[dim]Local skill already up-to-date ({new_version}).[/dim]")
    elif action == "installed":
        if is_quiet():
            print("installed")
        else:
            print_success(f"Local skill installed ({new_version}).")
    else:
        if is_quiet():
            print("updated")
        else:
            print_success(f"Local skill updated: {old_version} → {new_version}.")


@app.command("sync-all")
def sync_all_skills(
    json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
) -> None:
    """Sync all skills to latest versions (async task)."""

    async def _sync_all():
        api = get_skill_api()
        return await api.sync_all()

    try:
        result = asyncio.run(_sync_all())
    except APIError as e:
        print_error(e.message)
        raise typer.Exit(exit_code_for_api_error(e.code)) from None

    if json_output:
        print_json(result)
    elif is_quiet():
        print(result.get("task_id", ""))
    else:
        task_id = result.get("task_id", "?")
        message = result.get("message", "Sync started")
        print_success(f"{message} (task_id: {task_id})")
