"""CLI commands for skill discovery and marketplace.

This module provides commands to browse, install, and manage skills
from PyPI and other sources.
"""

from __future__ import annotations

import json
import os
import subprocess
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Any

import click
import httpx

from oclawma.cli_ui import (
    ErrorHelper,
    accent,
    bullet,
    header,
    highlight,
    info,
    key_value,
    muted,
    print_error,
    print_info,
    print_success,
    print_warning,
    progress_spinner,
    subheader,
    success,
    warning,
)
from oclawma.skills import SkillRegistry

if TYPE_CHECKING:
    pass


# PyPI API endpoints
PYPI_API_URL = "https://pypi.org/pypi/{package}/json"
PYPI_SEARCH_URL = "https://pypi.org/search/"
PYPI_SIMPLE_URL = "https://pypi.org/simple/"

# Skill naming convention
SKILL_PREFIX = "oclawma-skill-"


def get_pypi_package_info(package_name: str) -> dict[str, Any] | None:
    """Fetch package information from PyPI.

    Args:
        package_name: Name of the package

    Returns:
        Package info dict or None if not found
    """
    try:
        response = httpx.get(
            PYPI_API_URL.format(package=package_name),
            timeout=10,
            headers={"Accept": "application/json"},
        )
        if response.status_code == 200:
            return response.json()
        return None
    except httpx.HTTPError:
        return None


def search_pypi_skills(query: str | None = None, limit: int = 20) -> list[dict[str, Any]]:
    """Search PyPI for oclawma skills.

    Args:
        query: Optional search query
        limit: Maximum results to return

    Returns:
        List of package info dicts
    """
    results = []

    # Check if specific packages exist based on query
    packages_to_check = []

    if query:
        # Check for exact match first
        packages_to_check.append(f"{SKILL_PREFIX}{query}")
        # Check for query as part of name
        packages_to_check.append(f"{SKILL_PREFIX}{query.lower().replace(' ', '-')}")

    # Common known skills to check (these would be published on PyPI)
    known_skills = [
        "docker",
        "git",
        "kubernetes",
        "aws",
        "azure",
        "gcp",
        "github",
        "gitlab",
        "slack",
        "discord",
        "jira",
        "notion",
        "postgres",
        "mysql",
        "redis",
        "mongodb",
        "sqlite",
    ]

    for skill_name in known_skills:
        pkg_name = f"{SKILL_PREFIX}{skill_name}"
        if pkg_name not in packages_to_check:
            packages_to_check.append(pkg_name)

    # Check each package
    for pkg_name in packages_to_check[:limit]:
        info = get_pypi_package_info(pkg_name)
        if info:
            results.append(info)

    return results


def get_installed_skills() -> list[dict[str, Any]]:
    """Get list of installed oclawma skills.

    Returns:
        List of installed skill info dicts
    """
    skills = []

    try:
        # Use pip list to get installed packages
        result = subprocess.run(
            [sys.executable, "-m", "pip", "list", "--format=json"],
            capture_output=True,
            text=True,
            timeout=30,
        )

        if result.returncode == 0:
            packages = json.loads(result.stdout)
            for pkg in packages:
                name = pkg.get("name", "")
                if name.startswith(SKILL_PREFIX):
                    # Get more info about this skill
                    info = get_pypi_package_info(name)
                    skills.append(
                        {
                            "name": name,
                            "version": pkg.get("version", "unknown"),
                            "pypi_info": info,
                        }
                    )
    except (subprocess.TimeoutExpired, json.JSONDecodeError, subprocess.CalledProcessError):
        pass

    return skills


def get_skills_directory() -> Path:
    """Get the skills directory path.

    Returns:
        Path to skills directory
    """
    return Path(os.environ.get("OCLAWMA_SKILLS_PATH", os.path.expanduser("~/.oclawma/skills")))


@click.group(name="skills")
def skills_cli() -> None:
    """Browse and manage skills from PyPI."""
    pass


@skills_cli.command(name="search")
@click.argument("query", required=False)
@click.option("--limit", "-l", default=20, help="Maximum number of results")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON")
def search_skills(query: str | None, limit: int, json_output: bool) -> None:
    """Search for skills on PyPI.

    Searches PyPI for packages matching 'oclawma-skill-*' pattern.

    Examples:
        oclawma skills search docker
        oclawma skills search git --limit 10
        oclawma skills search --json
    """
    if not json_output:
        click.echo(
            f"{info('Searching PyPI for skills', icon=True)}{' matching: ' + highlight(query) if query else ''}..."
        )

    results = search_pypi_skills(query, limit)

    if json_output:
        # Simplified JSON output
        output = []
        for pkg in results:
            info_data = pkg.get("info", {})
            output.append(
                {
                    "name": info_data.get("name"),
                    "version": info_data.get("version"),
                    "description": info_data.get("summary"),
                    "author": info_data.get("author"),
                    "url": info_data.get("home_page")
                    or info_data.get("project_urls", {}).get("Homepage"),
                }
            )
        click.echo(json.dumps(output, indent=2))
        return

    if not results:
        print_warning("No skills found. Try a different search term.")
        return

    click.echo()
    click.echo(header(f"FOUND {len(results)} SKILL(S)", width=58))
    click.echo()

    for pkg in results:
        info_data = pkg.get("info", {})
        name = info_data.get("name", "unknown")
        version = info_data.get("version", "unknown")
        summary = info_data.get("summary", "No description available")
        author = info_data.get("author", "Unknown")

        # Remove prefix for display
        display_name = name[len(SKILL_PREFIX) :] if name.startswith(SKILL_PREFIX) else name

        click.echo(f"  {accent('•')} {highlight(display_name)} ({muted(version)})")
        click.echo(f"    {muted('Package:')} {name}")
        click.echo(f"    {summary}")
        if author and author != "Unknown":
            click.echo(f"    {muted('By:')} {author}")
        click.echo()


@skills_cli.command(name="install")
@click.argument("name")
@click.option("--version", "-v", help="Specific version to install")
@click.option("--upgrade", "-U", is_flag=True, help="Upgrade to latest version")
@click.option("--user", "-u", is_flag=True, help="Install in user site-packages")
def install_skill(name: str, version: str | None, upgrade: bool, user: bool) -> None:
    """Install a skill from PyPI.

    Installs a skill package from PyPI. The skill name can be provided
    with or without the 'oclawma-skill-' prefix.

    Examples:
        oclawma skills install docker
        oclawma skills install oclawma-skill-docker
        oclawma skills install git --version 1.0.0
        oclawma skills install aws --upgrade
    """
    # Normalize name
    if not name.startswith(SKILL_PREFIX):
        pkg_name = f"{SKILL_PREFIX}{name}"
    else:
        pkg_name = name
        name = name[len(SKILL_PREFIX) :]

    # Check if package exists on PyPI
    print_info(f"Checking PyPI for {pkg_name}...")

    with progress_spinner("Fetching package info..."):
        pkg_info_result = get_pypi_package_info(pkg_name)

    if not pkg_info_result:
        print_error(ErrorHelper.skill_not_found(name))
        return

    # Build install spec
    install_spec = pkg_name
    if version:
        install_spec = f"{pkg_name}=={version}"

    click.echo(f"{info('Installing', icon=True)} {highlight(install_spec)}...")

    # Build pip command
    cmd = [sys.executable, "-m", "pip", "install"]

    if upgrade:
        cmd.append("--upgrade")
    if user:
        cmd.append("--user")

    cmd.append(install_spec)

    # Run pip install
    try:
        result = subprocess.run(cmd, capture_output=False, text=True, timeout=300)

        if result.returncode == 0:
            print_success(f"Successfully installed {name}")

            # Show post-install info
            pkg_info_data = pkg_info_result.get("info", {})
            click.echo()
            click.echo(subheader("SKILL INFO"))
            click.echo(key_value("Name", name))
            click.echo(key_value("Version", pkg_info_data.get("version", "unknown")))
            click.echo(key_value("Description", pkg_info_data.get("summary", "N/A")))

            # Check for entry points
            try:
                from oclawma.skills.entry_points import EntryPointDiscovery

                discovery = EntryPointDiscovery()
                entry_points = list(discovery.discover())
                if entry_points:
                    click.echo(key_value("Entry points", str(len(entry_points))))
            except Exception:
                pass

            click.echo()
            print_info("The skill is now available. Restart oclawma to use it.")
        else:
            print_error(f"Installation failed with exit code {result.returncode}")
    except subprocess.TimeoutExpired:
        print_error("Installation timed out")
    except Exception as e:
        print_error(f"Installation failed: {e}")


@skills_cli.command(name="list")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON")
@click.option("--installed", "-i", is_flag=True, help="Show only PyPI-installed skills")
def list_skills(json_output: bool, installed: bool) -> None:
    """List installed skills.

    Shows all skills that are currently installed and available.

    Examples:
        oclawma skills list
        oclawma skills list --json
        oclawma skills list --installed
    """
    if installed:
        # Only show PyPI-installed skills
        if json_output:
            # Skip spinner for JSON output to avoid ANSI codes
            skills = get_installed_skills()
            output = []
            for skill in skills:
                pypi = skill.get("pypi_info", {}).get("info", {})
                output.append(
                    {
                        "name": skill["name"],
                        "version": skill["version"],
                        "description": pypi.get("summary"),
                        "author": pypi.get("author"),
                    }
                )
            click.echo(json.dumps(output, indent=2))
            return

        with progress_spinner("Loading installed skills..."):
            skills = get_installed_skills()

        if not skills:
            print_warning("No PyPI-installed skills found.")
            return

        click.echo(header(f"PYPI-INSTALLED SKILLS ({len(skills)})", width=58))
        click.echo()

        for skill in skills:
            name = skill["name"]
            version = skill["version"]
            pypi = skill.get("pypi_info", {}).get("info", {})
            summary = pypi.get("summary", "No description")

            display_name = name[len(SKILL_PREFIX) :] if name.startswith(SKILL_PREFIX) else name

            click.echo(f"  {accent('•')} {highlight(display_name)} ({muted(version)})")
            click.echo(f"    {muted('Package:')} {name}")
            click.echo(f"    {summary}")
            click.echo()

        return

    # Show all discovered skills (from filesystem + PyPI)
    skills_dir = get_skills_directory()

    if json_output:
        # Skip spinner for JSON output to avoid ANSI codes
        registry = SkillRegistry()

        if skills_dir.exists():
            registry.discover_skills(skills_dir)

        # Also get PyPI-installed skills
        pypi_skills = get_installed_skills()

        available = registry.list_available()
        loaded = registry.list_loaded()

        output = {
            "local_skills": [],
            "pypi_skills": [],
        }

        for skill_name in available:
            manifest = registry.get_manifest(skill_name)
            output["local_skills"].append(
                {
                    "name": skill_name,
                    "version": manifest.version,
                    "description": manifest.description,
                    "loaded": skill_name in loaded,
                }
            )

        for skill in pypi_skills:
            pypi = skill.get("pypi_info", {}).get("info", {})
            output["pypi_skills"].append(
                {
                    "name": skill["name"],
                    "version": skill["version"],
                    "description": pypi.get("summary"),
                }
            )

        click.echo(json.dumps(output, indent=2))
        return

    with progress_spinner("Discovering skills..."):
        registry = SkillRegistry()

        if skills_dir.exists():
            registry.discover_skills(skills_dir)

        # Also get PyPI-installed skills
        pypi_skills = get_installed_skills()
        {s["name"] for s in pypi_skills}

        available = registry.list_available()
        loaded = registry.list_loaded()

    # Display local skills
    click.echo(header("INSTALLED SKILLS", width=58))
    click.echo()

    if available:
        click.echo(subheader(f"Local Skills ({len(available)})"))
        click.echo()

        for skill_name in sorted(available):
            manifest = registry.get_manifest(skill_name)
            status = success("loaded") if skill_name in loaded else muted("discovered")

            click.echo(f"  {accent('•')} {highlight(skill_name)} [{status}]")
            if manifest.description:
                click.echo(f"    {manifest.description}")
            click.echo()
    else:
        print_info("No local skills found.")
        click.echo(f"   {muted('Skills directory:')} {skills_dir}")
        click.echo()

    # Display PyPI-installed skills
    if pypi_skills:
        click.echo(subheader(f"PyPI-Installed Skills ({len(pypi_skills)})"))
        click.echo()

        for skill in pypi_skills:
            name = skill["name"]
            version = skill["version"]
            pypi = skill.get("pypi_info", {}).get("info", {})
            summary = pypi.get("summary", "No description")

            display_name = name[len(SKILL_PREFIX) :] if name.startswith(SKILL_PREFIX) else name

            click.echo(f"  {accent('•')} {highlight(display_name)} ({muted(version)})")
            click.echo(f"    {muted('Package:')} {name}")
            click.echo(f"    {summary}")
            click.echo()
    else:
        print_info("No PyPI-installed skills found.")
        click.echo()
        click.echo(f"  {bullet('Find skills to install:')}")
        click.echo(f"    {highlight('oclawma skills search')}")


@skills_cli.command(name="info")
@click.argument("name")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON")
def skill_info(name: str, json_output: bool) -> None:
    """Show detailed information about a skill.

    Displays comprehensive information about an installed skill,
    including version, description, tools, and metadata.

    Examples:
        oclawma skill info docker
        oclawma skill info git --json
    """
    # Try to find the skill (local or PyPI)
    skills_dir = get_skills_directory()

    with progress_spinner("Loading skill info..."):
        registry = SkillRegistry()

        if skills_dir.exists():
            registry.discover_skills(skills_dir)

        # Normalize name
        pkg_name = name if name.startswith(SKILL_PREFIX) else f"{SKILL_PREFIX}{name}"

        info_dict: dict[str, Any] = {}
        found = False

        # Check if it's a local skill
        if registry.has_skill(name):
            found = True
            manifest = registry.get_manifest(name)
            info_dict = {
                "name": name,
                "source": "local",
                "version": manifest.version,
                "description": manifest.description,
                "author": manifest.author,
                "category": manifest.category,
                "tags": manifest.tags,
                "entry_point": manifest.entry_point,
                "tools": [tool.name for tool in manifest.tools],
                "requirements": manifest.requirements,
            }

        # Check if it's a PyPI-installed skill
        pypi_info = get_pypi_package_info(pkg_name)
        if pypi_info:
            found = True
            if not info_dict:
                # Only PyPI info available
                pkg = pypi_info.get("info", {})
                info_dict = {
                    "name": name,
                    "source": "pypi",
                    "version": pkg.get("version"),
                    "description": pkg.get("summary"),
                    "author": pkg.get("author"),
                    "license": pkg.get("license"),
                    "home_page": pkg.get("home_page"),
                    "package_url": pkg.get("package_url"),
                    "requires_python": pkg.get("requires_python"),
                    "requires_dist": pkg.get("requires_dist", []),
                }
            else:
                # Merge with PyPI info
                pkg = pypi_info.get("info", {})
                info_dict["pypi_version"] = pkg.get("version")
                info_dict["license"] = pkg.get("license")
                info_dict["home_page"] = pkg.get("home_page")
                info_dict["package_url"] = pkg.get("package_url")

    if not found:
        print_error(ErrorHelper.skill_not_found(name))
        return

    if json_output:
        click.echo(json.dumps(info_dict, indent=2))
        return

    # Display formatted info
    click.echo(header(f"SKILL: {info_dict.get('name', name)}", width=58))
    click.echo()

    if "version" in info_dict:
        click.echo(key_value("Version", info_dict["version"]))
    if "pypi_version" in info_dict:
        click.echo(key_value("PyPI Version", info_dict["pypi_version"]))
    if "source" in info_dict:
        click.echo(key_value("Source", info_dict["source"]))
    if info_dict.get("description"):
        click.echo(key_value("Description", info_dict["description"]))
    if info_dict.get("author"):
        click.echo(key_value("Author", info_dict["author"]))
    if info_dict.get("category"):
        click.echo(key_value("Category", info_dict["category"]))
    if info_dict.get("license"):
        click.echo(key_value("License", info_dict["license"]))

    if info_dict.get("tags"):
        click.echo(key_value("Tags", ", ".join(info_dict["tags"])))

    if info_dict.get("entry_point"):
        click.echo(key_value("Entry Point", info_dict["entry_point"]))

    if info_dict.get("home_page"):
        click.echo(key_value("Homepage", info_dict["home_page"]))

    if info_dict.get("package_url"):
        click.echo(key_value("PyPI URL", info_dict["package_url"]))

    if info_dict.get("tools"):
        click.echo()
        click.echo(subheader(f"Tools ({len(info_dict['tools'])})"))
        for tool in info_dict["tools"]:
            click.echo(f"  {bullet(tool)}")

    if info_dict.get("requirements"):
        click.echo()
        click.echo(subheader("Requirements"))
        for req in info_dict["requirements"][:10]:  # Limit to 10
            click.echo(f"  {bullet(req)}")
        if len(info_dict["requirements"]) > 10:
            remaining = len(info_dict["requirements"]) - 10
            click.echo(f"  {muted(f'... and {remaining} more')}")

    click.echo()


@skills_cli.command(name="uninstall")
@click.argument("name")
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
def uninstall_skill(name: str, yes: bool) -> None:
    """Uninstall a skill.

    Removes a PyPI-installed skill from the system.

    Examples:
        oclawma skills uninstall docker
        oclawma skills uninstall git --yes
    """
    # Normalize name
    if not name.startswith(SKILL_PREFIX):
        pkg_name = f"{SKILL_PREFIX}{name}"
    else:
        pkg_name = name
        name = name[len(SKILL_PREFIX) :]

    # Check if installed
    with progress_spinner("Checking installation..."):
        installed = get_installed_skills()

    installed_names = {s["name"] for s in installed}

    if pkg_name not in installed_names:
        print_error(f"Skill '{name}' is not installed via PyPI.")
        return

    # Confirm uninstall
    if not yes and not click.confirm(warning(f"Uninstall {name} ({pkg_name})?")):
        print_info("Uninstall cancelled")
        return

    print_info(f"Uninstalling {pkg_name}...")

    # Run pip uninstall
    try:
        result = subprocess.run(
            [sys.executable, "-m", "pip", "uninstall", "-y", pkg_name],
            capture_output=False,
            text=True,
            timeout=120,
        )

        if result.returncode == 0:
            print_success(f"Successfully uninstalled {name}")
        else:
            print_error(f"Uninstall failed with exit code {result.returncode}")
    except subprocess.TimeoutExpired:
        print_error("Uninstall timed out")
    except Exception as e:
        print_error(f"Uninstall failed: {e}")


@skills_cli.command(name="update")
@click.argument("name", required=False)
@click.option("--all", "update_all", is_flag=True, help="Update all skills")
def update_skill(name: str | None, update_all: bool) -> None:
    """Update installed skills to the latest version.

    Updates one or all PyPI-installed skills to their latest versions.

    Examples:
        oclawma skills update docker
        oclawma skills update --all
    """
    if not name and not update_all:
        print_error("Please specify a skill name or use --all")
        return

    if update_all:
        # Update all installed skills
        with progress_spinner("Loading installed skills..."):
            installed = get_installed_skills()

        if not installed:
            print_info("No PyPI-installed skills found.")
            return

        click.echo(f"{info('Updating', icon=True)} {len(installed)} skill(s)...")
        click.echo()

        success_count = 0
        fail_count = 0

        for skill in installed:
            pkg_name = skill["name"]
            display_name = (
                pkg_name[len(SKILL_PREFIX) :] if pkg_name.startswith(SKILL_PREFIX) else pkg_name
            )

            click.echo(f"  {accent('→')} Updating {highlight(display_name)}...", nl=False)

            try:
                result = subprocess.run(
                    [sys.executable, "-m", "pip", "install", "--upgrade", pkg_name],
                    capture_output=True,
                    text=True,
                    timeout=300,
                )

                if result.returncode == 0:
                    click.echo(f" {success('✓')}")
                    success_count += 1
                else:
                    click.echo(f" {warning('✗')}")
                    fail_count += 1
            except Exception:
                click.echo(f" {warning('✗')}")
                fail_count += 1

        click.echo()
        if fail_count == 0:
            print_success(f"Updated all {success_count} skill(s)")
        else:
            print_warning(f"Updated {success_count}, failed {fail_count}")
    else:
        # Update specific skill
        if not name.startswith(SKILL_PREFIX):
            pkg_name = f"{SKILL_PREFIX}{name}"
        else:
            pkg_name = name
            name = name[len(SKILL_PREFIX) :]

        # Check if installed
        with progress_spinner("Checking installation..."):
            installed = get_installed_skills()

        installed_names = {s["name"] for s in installed}

        if pkg_name not in installed_names:
            print_error(f"Skill '{name}' is not installed.")
            return

        print_info(f"Updating {name}...")

        try:
            result = subprocess.run(
                [sys.executable, "-m", "pip", "install", "--upgrade", pkg_name],
                capture_output=False,
                text=True,
                timeout=300,
            )

            if result.returncode == 0:
                # Get new version
                pkg_info_result = get_pypi_package_info(pkg_name)
                new_version = (
                    pkg_info_result.get("info", {}).get("version", "unknown")
                    if pkg_info_result
                    else "unknown"
                )
                print_success(f"Successfully updated {name} to {new_version}")
            else:
                print_error(f"Update failed with exit code {result.returncode}")
        except subprocess.TimeoutExpired:
            print_error("Update timed out")
        except Exception as e:
            print_error(f"Update failed: {e}")


# Add alias: skill (singular) points to skills group
@click.group(name="skill")
def skill_cli() -> None:
    """Commands for working with a specific skill (alias for 'skills')."""
    pass


@skill_cli.command(name="info")
@click.argument("name")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON")
def skill_info_alias(name: str, json_output: bool) -> None:
    """Show detailed information about a skill.

    This is an alias for 'oclawma skills info'.
    """
    # Call the actual implementation
    ctx = click.get_current_context()
    ctx.invoke(skill_info, name=name, json_output=json_output)


# Export the CLI groups
__all__ = ["skills_cli", "skill_cli"]
