"""alice compose — Interactive wizard for composing a Strands agent."""

from __future__ import annotations

import os
from pathlib import Path
from typing import Any

import click

from alice_cli import personality
from alice_cli.compose_engine import (
    AVAILABLE_TOOLS,
    AgentSpec,
    render_agent_code,
)
from alice_cli.console import err_console, print_banner, print_success

# ---------------------------------------------------------------------------
# Provider metadata (shared by both interactive and non-interactive paths)
# ---------------------------------------------------------------------------

_PROVIDER_CHOICES = [
    ("1", "bedrock", "Amazon Bedrock (default)"),
    ("2", "ollama", "Ollama (local)"),
    ("3", "litellm", "LiteLLM"),
    ("4", "custom", "Custom provider"),
]

_PROVIDER_NAMES = [c[1] for c in _PROVIDER_CHOICES]
_PROVIDER_LOOKUP = {c[0]: c[1] for c in _PROVIDER_CHOICES}
# Also allow typing the provider name directly
_PROVIDER_LOOKUP.update({c[1]: c[1] for c in _PROVIDER_CHOICES})

# ---------------------------------------------------------------------------
# prompt_toolkit helpers (lazy-imported to avoid hard failure if missing)
# ---------------------------------------------------------------------------


def _make_pt_session() -> Any:
    """Create a prompt_toolkit PromptSession. Returns None on import failure."""
    try:
        from prompt_toolkit import PromptSession

        return PromptSession()
    except ImportError:
        return None


# ---------------------------------------------------------------------------
# prompt_toolkit–powered prompts (interactive / enhanced mode)
# ---------------------------------------------------------------------------


def _pt_prompt_agent_name(session: Any) -> str:
    """Prompt for agent name using prompt_toolkit."""
    result = session.prompt("  Agent name [my_agent]: ")
    return result.strip() or "my_agent"


def _pt_prompt_system_prompt(session: Any) -> str:
    """Multi-line system prompt via prompt_toolkit.

    The user types freely across multiple lines and submits with
    Escape followed by Enter (Meta+Enter).  An empty submission
    falls back to the default.
    """
    from prompt_toolkit.key_binding import KeyBindings

    bindings = KeyBindings()

    @bindings.add("escape", "enter")
    def _submit(event: Any) -> None:
        event.current_buffer.validate_and_handle()

    err_console.print(
        "  [dim]Enter system prompt (multi-line). " "Press Escape+Enter to submit.[/dim]"
    )
    result = session.prompt(
        "  System prompt: ",
        multiline=True,
        key_bindings=bindings,
    )
    return result.strip() or "You are a helpful assistant."


def _pt_prompt_model(session: Any) -> str:
    """Prompt for model provider with WordCompleter."""
    from prompt_toolkit.completion import WordCompleter

    err_console.print("\n  [dim]Available models:[/dim]")
    for num, _, desc in _PROVIDER_CHOICES:
        err_console.print(f"    [{num}] {desc}")

    completer = WordCompleter(_PROVIDER_NAMES, ignore_case=True)
    result = session.prompt(
        "\n  Model provider [bedrock]: ",
        completer=completer,
    )
    selection = result.strip() or "1"
    return _PROVIDER_LOOKUP.get(selection, "bedrock")


def _pt_prompt_tools(session: Any) -> list[str]:
    """Prompt for tool selection with WordCompleter."""
    from prompt_toolkit.completion import WordCompleter

    err_console.print("\n  [dim]Available Strands tools:[/dim]")
    for i, tool in enumerate(AVAILABLE_TOOLS, 1):
        err_console.print(f"    [{i:>2}] {tool.name:<20} {tool.description}")

    tool_names = [t.name for t in AVAILABLE_TOOLS]
    completer = WordCompleter(tool_names, ignore_case=True)

    err_console.print(
        "\n  [dim]Enter tool names or numbers separated by commas, " "or 'none'[/dim]"
    )
    raw = session.prompt("  Tools [none]: ", completer=completer)

    if not raw.strip() or raw.strip().lower() == "none":
        return []

    tool_name_set = {t.name for t in AVAILABLE_TOOLS}
    selected: list[str] = []
    for part in raw.split(","):
        part = part.strip()
        if part in tool_name_set:
            selected.append(part)
        elif part.isdigit():
            idx = int(part) - 1
            if 0 <= idx < len(AVAILABLE_TOOLS):
                selected.append(AVAILABLE_TOOLS[idx].name)
    return selected


def _pt_prompt_output_path(session: Any, agent_name: str) -> Path:
    """Prompt for output path using prompt_toolkit."""
    default = os.path.join("agents", f"{agent_name}.py")
    result = session.prompt(f"  Output path [{default}]: ")
    return Path(result.strip() or default)


# ---------------------------------------------------------------------------
# click.prompt fallbacks (non-interactive / legacy mode)
# ---------------------------------------------------------------------------


def _click_prompt_agent_name() -> str:
    return str(
        click.prompt(
            click.style("  Agent name", fg="yellow"),
            default="my_agent",
            show_default=True,
        )
    )


def _click_prompt_system_prompt() -> str:
    return str(
        click.prompt(
            click.style("  System prompt", fg="yellow"),
            default="You are a helpful assistant.",
            show_default=True,
        )
    )


def _click_prompt_model() -> str:
    err_console.print("\n  [dim]Available models:[/dim]")
    for num, _, desc in _PROVIDER_CHOICES:
        err_console.print(f"    [{num}] {desc}")

    selection = click.prompt(
        click.style("\n  Model provider", fg="yellow"),
        default="1",
        show_default=True,
    )
    return _PROVIDER_LOOKUP.get(selection, "bedrock")


def _click_prompt_tools() -> list[str]:
    err_console.print("\n  [dim]Available Strands tools:[/dim]")
    for i, tool in enumerate(AVAILABLE_TOOLS, 1):
        err_console.print(f"    [{i:>2}] {tool.name:<20} {tool.description}")

    err_console.print("\n  [dim]Enter numbers separated by commas, or 'none'[/dim]")
    raw = click.prompt(
        click.style("  Tools", fg="yellow"),
        default="none",
        show_default=True,
    )

    if raw.strip().lower() == "none":
        return []

    selected: list[str] = []
    for part in raw.split(","):
        part = part.strip()
        if part.isdigit():
            idx = int(part) - 1
            if 0 <= idx < len(AVAILABLE_TOOLS):
                selected.append(AVAILABLE_TOOLS[idx].name)
    return selected


def _click_prompt_output_path(agent_name: str) -> Path:
    default = os.path.join("agents", f"{agent_name}.py")
    raw = click.prompt(
        click.style("  Output path", fg="yellow"),
        default=default,
        show_default=True,
    )
    return Path(raw)


# ---------------------------------------------------------------------------
# Command
# ---------------------------------------------------------------------------


@click.command("compose")
@click.option("--name", default=None, help="Agent name (skips interactive prompt)")
@click.option("--output", "-o", default=None, type=click.Path(), help="Output file path")
@click.pass_context
def compose(ctx: click.Context, name: str | None, output: str | None) -> None:
    """Build a Strands agent interactively.

    Walks through choosing a name, model provider, system prompt, and tools,
    then generates a ready-to-run Python agent file.
    """
    config = ctx.obj["config"]
    print_banner(personality.BANNER, config.quiet)

    err_console.print("  [bold]Compose a new Strands Agent[/bold]\n")

    # Non-interactive mode: --name provided → use click.prompt for any
    # remaining prompts (Requirement 10.5).
    non_interactive = name is not None
    pt_session = None if non_interactive else _make_pt_session()

    if pt_session is not None:
        # Enhanced interactive mode with prompt_toolkit
        agent_name = _pt_prompt_agent_name(pt_session)
        system_prompt = _pt_prompt_system_prompt(pt_session)
        model_provider = _pt_prompt_model(pt_session)
        tools = _pt_prompt_tools(pt_session)
        output_path = Path(output) if output else _pt_prompt_output_path(pt_session, agent_name)
    else:
        # Fallback: click.prompt (non-interactive or prompt_toolkit unavailable)
        agent_name = name or _click_prompt_agent_name()
        system_prompt = _click_prompt_system_prompt()
        model_provider = _click_prompt_model()
        tools = _click_prompt_tools()
        output_path = Path(output) if output else _click_prompt_output_path(agent_name)

    spec = AgentSpec(
        name=agent_name,
        system_prompt=system_prompt,
        model_provider=model_provider,
        tools=tools,
    )

    code = render_agent_code(spec)

    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(code)

    print_success(f"Agent written to {output_path}", config.quiet)
    err_console.print(f"\n  [dim]Run it with:[/dim]  python {output_path}")
