"""
Module: add_command
Description: Add a new command to an existing Intelligence Pack

Implements:
    - docs/cli/reference.md#huitzo-add-command

See Also:
    - docs/sdk/commands.md (for command patterns)
    - docs/packs/manifest.md#commands-section
"""

from __future__ import annotations

import keyword
import re
from pathlib import Path
from typing import Optional

import typer
import yaml
from jinja2 import Environment, PackageLoader
from rich.console import Console

console = Console()

# "default" means "omit queue field, let the platform decide" (maps to auto).
VALID_QUEUES = ("fast", "medium", "long", "default")
AVAILABLE_SERVICES = (
    "llm:complete",
    "storage:read",
    "storage:write",
    "http:request",
    "email:send",
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def validate_command_name(name: str) -> bool:
    """Validate command name: lowercase, hyphens, digits only."""
    return bool(re.match(r"^[a-z][a-z0-9]*(-[a-z0-9]+)*$", name))


def command_name_to_function(name: str) -> str:
    """Convert 'analyze-data' -> 'analyze_data'."""
    return name.replace("-", "_")


def command_name_to_file(name: str) -> str:
    """Convert 'analyze-data' -> 'analyze_data' (file stem).

    Intentionally identical to command_name_to_function — kept separate
    for semantic clarity (file names vs Python identifiers).
    """
    return name.replace("-", "_")


def command_name_to_class(name: str) -> str:
    """Convert 'analyze-data' -> 'AnalyzeDataArgs'."""
    parts = name.split("-")
    return "".join(p.capitalize() for p in parts) + "Args"


# ---------------------------------------------------------------------------
# Pack context detection
# ---------------------------------------------------------------------------


class PackContext:
    """Detected context of the current pack directory."""

    def __init__(
        self,
        pack_root: Path,
        pack_name: str,
        namespace: str,
        module_name: str,
        existing_commands: list[str],
    ) -> None:
        self.pack_root = pack_root
        self.pack_name = pack_name
        self.namespace = namespace
        self.module_name = module_name
        self.existing_commands = existing_commands


def detect_pack_context(path: Path | None = None) -> PackContext:
    """Auto-detect pack context from huitzo.yaml and pyproject.toml.

    Raises:
        typer.Exit: if not inside a valid pack directory.
    """
    pack_root = path or Path.cwd()

    huitzo_yaml = pack_root / "huitzo.yaml"
    pyproject_toml = pack_root / "pyproject.toml"

    if not huitzo_yaml.exists():
        console.print(
            "[red]❌ No huitzo.yaml found. Are you inside an Intelligence Pack directory?[/red]"
        )
        raise typer.Exit(1)

    if not pyproject_toml.exists():
        console.print("[red]❌ No pyproject.toml found in pack root.[/red]")
        raise typer.Exit(1)

    # Parse huitzo.yaml
    try:
        with open(huitzo_yaml) as f:
            manifest = yaml.safe_load(f)
    except yaml.YAMLError as e:
        console.print(f"[red]❌ huitzo.yaml contains invalid YAML: {e}[/red]")
        raise typer.Exit(1)

    if not isinstance(manifest, dict):
        console.print("[red]❌ huitzo.yaml is empty or has invalid structure.[/red]")
        raise typer.Exit(1)

    pack_section = manifest.get("pack", {})
    pack_name = pack_section.get("name", "")
    namespace = pack_section.get("namespace", "")

    if not pack_name or not namespace:
        console.print("[red]❌ huitzo.yaml is missing pack name or namespace.[/red]")
        raise typer.Exit(1)

    module_name = pack_name.replace("-", "_")

    # Collect existing command names from huitzo.yaml
    existing_commands = [cmd.get("name", "") for cmd in manifest.get("commands", [])]

    return PackContext(
        pack_root=pack_root,
        pack_name=pack_name,
        namespace=namespace,
        module_name=module_name,
        existing_commands=existing_commands,
    )


# ---------------------------------------------------------------------------
# File modification helpers
# ---------------------------------------------------------------------------


def add_entry_point_to_pyproject(
    pyproject_path: Path,
    namespace: str,
    pack_name: str,
    command_name: str,
    module_name: str,
    command_file: str,
    command_func: str,
) -> None:
    """Insert a new entry point line into [project.entry-points."huitzo.commands"]."""
    content = pyproject_path.read_text()
    entry_point = (
        f'"@{namespace}/{pack_name}/{command_name}" = '
        f'"{module_name}.commands.{command_file}:{command_func}"'
    )

    # Find the entry-points section
    marker = '[project.entry-points."huitzo.commands"]'
    idx = content.find(marker)
    if idx == -1:
        # Section missing — append it before [build-system] or at end
        build_idx = content.find("[build-system]")
        section_block = f"\n{marker}\n{entry_point}\n"
        if build_idx != -1:
            content = content[:build_idx] + section_block + "\n" + content[build_idx:]
        else:
            content = content.rstrip() + "\n" + section_block
    else:
        # Find the end of the existing entry-points block.
        # Walk from the marker until we hit a blank line or a new section header.
        after_marker = idx + len(marker)
        lines = content[after_marker:].split("\n")
        insert_after = after_marker
        for line in lines:
            insert_after += len(line) + 1  # +1 for the newline
            stripped = line.strip()
            # If we hit a new section header or a blank line, stop here.
            if stripped.startswith("[") and stripped != marker:
                # Back up before this section header line
                insert_after -= len(line) + 1
                break
            if stripped == "" and insert_after > after_marker + 1:
                # Back up before blank line
                insert_after -= 1
                break

        content = content[:insert_after] + entry_point + "\n" + content[insert_after:]

    pyproject_path.write_text(content)


def add_command_to_huitzo_yaml(
    yaml_path: Path,
    command_name: str,
    description: str,
    *,
    permissions: list[str] | None = None,
    timeout: int = 60,
    queue: str = "default",
    retries: int = 3,
) -> None:
    """Append a command entry to the commands list in huitzo.yaml."""
    content = yaml_path.read_text()

    # Build the YAML block for the new command.
    # Quote the description to handle YAML-special characters (colons, #, etc.).
    safe_desc = description.replace("\\", "\\\\").replace('"', '\\"')
    block_lines = [
        f"  - name: {command_name}",
        f'    description: "{safe_desc}"',
        "    enabled: true",
    ]
    if timeout != 60:
        block_lines.append(f"    timeout: {timeout}")
    if queue != "default":
        block_lines.append(f"    queue: {queue}")
    if retries != 3:
        block_lines.append(f"    retries: {retries}")
    if permissions:
        block_lines.append("    permissions:")
        for perm in permissions:
            block_lines.append(f"      - {perm}")

    new_block = "\n".join(block_lines) + "\n"

    # Find the commands: section and append
    if "commands:" not in content:
        content = content.rstrip() + "\n\ncommands:\n" + new_block
    else:
        content = content.rstrip() + "\n" + new_block

    yaml_path.write_text(content)


def update_commands_init(
    init_path: Path,
    command_file: str,
    command_func: str,
) -> None:
    """Add import and __all__ entry to commands/__init__.py."""
    if not init_path.exists():
        # Create fresh
        init_path.write_text(
            f'"""Commands for this pack."""\n\n'
            f"from .{command_file} import {command_func}\n\n"
            f'__all__ = ["{command_func}"]\n'
        )
        return

    content = init_path.read_text()
    import_line = f"from .{command_file} import {command_func}"

    # Avoid duplicate imports
    if import_line in content:
        return

    # Append import before __all__ if it exists, otherwise at end of imports
    if "__all__" in content:
        # Insert import before __all__
        all_idx = content.index("__all__")
        content = content[:all_idx] + import_line + "\n" + content[all_idx:]

        # Add to __all__ list
        # Match __all__ = ["..."] or __all__ = [\n...]
        all_pattern = re.compile(r"(__all__\s*=\s*\[)(.*?)(\])", re.DOTALL)
        match = all_pattern.search(content)
        if match:
            existing = match.group(2).strip()
            if existing:
                new_all = f'{existing}, "{command_func}"'
            else:
                new_all = f'"{command_func}"'
            content = (
                content[: match.start()]
                + f"{match.group(1)}{new_all}{match.group(3)}"
                + content[match.end() :]
            )
    else:
        # No __all__ — add import and create __all__
        content = content.rstrip() + f'\n{import_line}\n\n__all__ = ["{command_func}"]\n'

    init_path.write_text(content)


# ---------------------------------------------------------------------------
# Main command
# ---------------------------------------------------------------------------


def add_command(
    name: Optional[str] = typer.Argument(None, help="Command name (lowercase, hyphens OK)"),
    description: Optional[str] = typer.Option(
        None, "--description", "-d", help="Command description"
    ),
    permissions: Optional[str] = typer.Option(
        None,
        "--permissions",
        "-p",
        help="Comma-separated permissions (e.g. llm:complete,storage:write)",
    ),
    timeout: int = typer.Option(60, "--timeout", "-t", help="Timeout in seconds"),
    queue: str = typer.Option(
        "default", "--queue", "-q", help="Queue: fast, medium, long, default"
    ),
    retries: int = typer.Option(3, "--retries", "-r", help="Number of retries"),
    pydantic: bool = typer.Option(True, "--pydantic/--no-pydantic", help="Use Pydantic args model"),
    path: Optional[Path] = typer.Option(None, "--path", help="Path to pack root (default: cwd)"),
) -> None:
    """Add a new command to an existing Intelligence Pack.

    Scaffolds a command file, test file, and updates pyproject.toml,
    huitzo.yaml, and commands/__init__.py automatically.
    """
    # Detect pack context
    ctx = detect_pack_context(path)

    console.print(f'\n  Adding command to pack "[bold]{ctx.pack_name}[/bold]" (@{ctx.namespace})\n')

    # --- Gather inputs (interactive if not provided) ---

    if not name:
        name = typer.prompt("  Command name (lowercase, hyphens OK)")

    if not validate_command_name(name):
        console.print(
            "[red]❌ Invalid command name. Use lowercase letters, numbers, "
            "and hyphens (e.g., analyze-data).[/red]"
        )
        raise typer.Exit(1)

    # Check duplicate
    if name in ctx.existing_commands:
        console.print(f'[red]❌ Command "{name}" already exists in this pack.[/red]')
        raise typer.Exit(1)

    # Validate function name is a valid Python identifier and not a keyword
    func_name = command_name_to_function(name)
    if keyword.iskeyword(func_name) or not func_name.isidentifier():
        console.print(
            f'[red]❌ Command name "{name}" produces an invalid Python identifier '
            f'("{func_name}"). Choose a different name.[/red]'
        )
        raise typer.Exit(1)

    if not description:
        description = typer.prompt("  Description", default=f"The {name} command")

    # Validate queue
    if queue not in VALID_QUEUES:
        console.print(
            f"[red]❌ Invalid queue '{queue}'. Valid options: {', '.join(VALID_QUEUES)}[/red]"
        )
        raise typer.Exit(1)

    # Parse permissions
    perm_list: list[str] = []
    if permissions is not None:
        perm_list = [p.strip() for p in permissions.split(",") if p.strip()]
    else:
        # Interactive prompt — only when --permissions was not provided at all
        console.print(f"  [dim]Available: {', '.join(AVAILABLE_SERVICES)}[/dim]")
        raw = typer.prompt(
            "  Services / Permissions (comma-separated, Enter to skip)",
            default="",
        )
        if raw.strip():
            perm_list = [p.strip() for p in raw.split(",") if p.strip()]

    # --- Validate pack structure ---

    src_dir = ctx.pack_root / "src" / ctx.module_name
    commands_dir = src_dir / "commands"

    # Check for flat commands.py (not a directory)
    flat_commands = src_dir / "commands.py"
    if flat_commands.exists() and not commands_dir.exists():
        console.print(
            "[red]❌ Found flat commands.py instead of commands/ directory.[/red]\n"
            "[yellow]  Restructure to commands/ directory layout before using add-command.[/yellow]"
        )
        raise typer.Exit(1)

    # Create commands/ directory if missing
    if not commands_dir.exists():
        commands_dir.mkdir(parents=True, exist_ok=True)

    # Check command file doesn't already exist
    command_file = command_name_to_file(name)
    command_path = commands_dir / f"{command_file}.py"
    if command_path.exists():
        console.print(
            f"[red]❌ File already exists: {command_path.relative_to(ctx.pack_root)}[/red]"
        )
        raise typer.Exit(1)

    # --- Create / update files ---

    console.print("  Creating files...")

    # 1. Render command file from template
    env = Environment(loader=PackageLoader("huitzo_cli", "templates/pack"))
    template_ctx = {
        "command_name": name,
        "command_func": func_name,
        "command_file": command_file,
        "args_class": command_name_to_class(name),
        "namespace": ctx.namespace,
        "module_name": ctx.module_name,
        "description": description,
        "timeout": timeout,
        "use_pydantic": pydantic,
    }

    cmd_template = env.get_template("command.py.j2")
    command_path.write_text(cmd_template.render(**template_ctx))
    console.print(f"    [green]Created[/green]  {command_path.relative_to(ctx.pack_root)}")

    # 2. Update commands/__init__.py
    init_path = commands_dir / "__init__.py"
    update_commands_init(init_path, command_file, func_name)
    console.print(f"    [green]Updated[/green]  {init_path.relative_to(ctx.pack_root)}")

    # 3. Update pyproject.toml
    pyproject_path = ctx.pack_root / "pyproject.toml"
    add_entry_point_to_pyproject(
        pyproject_path,
        ctx.namespace,
        ctx.pack_name,
        name,
        ctx.module_name,
        command_file,
        func_name,
    )
    console.print("    [green]Updated[/green]  pyproject.toml")

    # 4. Update huitzo.yaml
    yaml_path = ctx.pack_root / "huitzo.yaml"
    add_command_to_huitzo_yaml(
        yaml_path,
        name,
        description,
        permissions=perm_list if perm_list else None,
        timeout=timeout,
        queue=queue,
        retries=retries,
    )
    console.print("    [green]Updated[/green]  huitzo.yaml")

    # 5. Create test file
    tests_dir = ctx.pack_root / "tests"
    tests_dir.mkdir(parents=True, exist_ok=True)
    test_path = tests_dir / f"test_{command_file}.py"

    test_template = env.get_template("test_command.py.j2")
    test_path.write_text(test_template.render(**template_ctx))
    console.print(f"    [green]Created[/green]  {test_path.relative_to(ctx.pack_root)}")

    # --- Summary ---

    console.print(
        f'\n  [green]Done![/green] Command "@{ctx.namespace}/{ctx.pack_name}/{name}" added.\n'
    )
    console.print("  [bold]Next steps:[/bold]")
    console.print(f"    1. Edit {command_path.relative_to(ctx.pack_root)}")
    console.print("    2. Run: huitzo pack test")
    console.print("    3. Run: huitzo pack dev")
