"""
Skills management commands.

List, show, create, update, and run agent skills (SKILL.md files).
"""

import logging
from pathlib import Path

import click
from rich import box
from rich.table import Table

from cliver.cli import Cliver, pass_cliver
from cliver.commands import click_help, wants_help
from cliver.messages import CLIverMessage
from cliver.skill_manager import SkillManager, validate_skill_name
from cliver.util import get_config_dir

logger = logging.getLogger(__name__)


@click.group(
    name="skills",
    help="Manage agent skills (SKILL.md files that guide LLM behavior for specific tasks)",
    invoke_without_command=True,
)
@pass_cliver
@click.pass_context
def skills(ctx, cliver: Cliver):
    """List, create, or manage skills."""
    if ctx.invoked_subcommand is None:
        _list_skills(cliver)


def _create_skill(cliver: Cliver, name: str, description: str, save_global: bool = False):
    """Create a new skill using LLM generation."""
    # Validate the skill name
    errors = validate_skill_name(name)
    if errors:
        for err in errors:
            cliver.output(f"{err}")
        return

    # Determine target directory
    if save_global:
        skills_dir = get_config_dir() / "skills" / name
    else:
        skills_dir = Path.cwd() / ".cliver" / "skills" / name

    skill_file = skills_dir / "SKILL.md"
    if skill_file.exists():
        cliver.output(f"Skill '{name}' already exists at {skill_file}")
        return

    # Build prompt for the LLM to generate the SKILL.md content
    prompt = _build_create_prompt(name, description)
    model = (cliver.session_options or {}).get("model") or None

    result = _llm_generate(cliver, prompt, model, f"Generating skill '{name}'...")
    if not result:
        return
    content = result

    # Write to disk
    skills_dir.mkdir(parents=True, exist_ok=True)
    skill_file.write_text(content, encoding="utf-8")
    cliver.output(f"Created skill at: {skill_file}")
    cliver.output("You can edit the file to refine the skill content.")


def _show_skill(cliver: Cliver, name: str):
    """Display the full content of a skill."""
    manager = SkillManager()
    skill = manager.get_skill(name)
    if not skill:
        cliver.output(f"Skill '{name}' not found.")
        _suggest_available(cliver, manager)
        return

    cliver.output(f"Skill: {skill.name}")
    cliver.output(f"Source: {skill.source} — {skill.base_dir}")
    cliver.output(f"Description: {skill.description}")
    if skill.allowed_tools:
        cliver.output(f"Allowed tools: {', '.join(skill.allowed_tools)}")
    cliver.output("")
    cliver.output(skill.body)


def _edit_skill(cliver: Cliver, name: str):
    """Open a skill's SKILL.md in $EDITOR."""
    import os

    from cliver.commands.identity import _run_editor

    manager = SkillManager()
    skill = manager.get_skill(name)
    if not skill:
        cliver.output(f"Skill '{name}' not found.")
        _suggest_available(cliver, manager)
        return

    skill_file = skill.base_dir / "SKILL.md"
    if not skill_file.exists():
        cliver.output(f"Skill file not found: {skill_file}")
        return

    editor = os.environ.get("EDITOR", "vi")
    try:
        _run_editor(editor, str(skill_file))
        cliver.output(f"Saved {skill_file}")
    except Exception as e:
        cliver.output(f"Error: {e}")


def _update_skill(cliver: Cliver, name: str, instructions: str):
    """Update an existing skill using LLM generation."""
    manager = SkillManager()
    skill = manager.get_skill(name)
    if not skill:
        cliver.output(f"Skill '{name}' not found.")
        _suggest_available(cliver, manager)
        return

    skill_file = skill.base_dir / "SKILL.md"
    if not skill_file.is_file():
        cliver.output(f"Cannot find SKILL.md at {skill_file}")
        return

    current_content = skill_file.read_text(encoding="utf-8")
    prompt = _build_update_prompt(name, current_content, instructions)
    model = (cliver.session_options or {}).get("model") or None

    result = _llm_generate(cliver, prompt, model, f"Updating skill '{name}'...")
    if not result:
        return
    content = result

    skill_file.write_text(content, encoding="utf-8")
    cliver.output(f"Updated skill at: {skill_file}")


def _compress_if_needed(cliver, agent_core, model_config, model_name, new_input):
    """Check and compress conversation history — skipped in v1 (needs porting)."""
    return


def _resolve_prompt(cliver: Cliver, inline_prompt: str, prompt_file: str | None) -> str | None:
    """Build the user prompt from inline text and/or a file.

    Returns the combined prompt string, or None on error.
    """
    parts = []
    if prompt_file:
        p = Path(prompt_file).expanduser()
        if not p.is_file():
            cliver.output(f"Prompt file not found: {p}")
            return None
        try:
            parts.append(p.read_text(encoding="utf-8").strip())
        except Exception as e:
            cliver.output(f"Failed to read prompt file: {e}")
            return None
    if inline_prompt:
        parts.append(inline_prompt)
    return "\n\n".join(parts) if parts else ""


def _run_skill(cliver: Cliver, name: str, message: str = ""):
    """Activate a skill and run it through LLM inference."""
    manager = SkillManager()
    skill_obj = manager.get_skill(name)
    if not skill_obj:
        cliver.output(f"Skill '{name}' not found.")
        _suggest_available(cliver, manager)
        return

    cliver.output(f"Activating skill '{name}' ...")

    user_message = message if message else ""
    if not user_message:
        user_message = (
            f"I want to use the '{name}' skill. Please explain what this skill does and ask me what I'd like to do."
        )

    from cliver.cli_llm_call import LLMCallOptions, llm_call

    session_options = cliver.session_options or {}
    use_model = session_options.get("model", None)
    use_stream = session_options.get("stream", True)
    cliver.record_turn("user", user_message)

    conv_history = list(cliver.conversation_messages) if cliver.conversation_messages else None
    cliver.conversation_messages.append(CLIverMessage(role="user", content=user_message))

    def on_response(text: str):
        cliver.record_turn("assistant", text)
        cliver.conversation_messages.append(CLIverMessage(role="assistant", content=text))

    router = getattr(cliver, "_command_router", None)
    if router is not None:
        router.promote_to_query()

    llm_call(
        cliver,
        LLMCallOptions(
            user_input=user_message,
            model=use_model,
            stream=use_stream,
            conversation_history=conv_history,
            on_response=on_response,
        ),
    )


# ---------------------------------------------------------------------------
# Dispatch function
# ---------------------------------------------------------------------------


_SUBCOMMANDS: dict[str, click.Command] = {}


def dispatch(cliver: Cliver, args: str):
    """List and manage skills — list, show, run, create, update."""
    parts = args.strip().split(None, 1) if args.strip() else []
    sub = parts[0] if parts else "list"
    rest = parts[1] if len(parts) > 1 else ""

    if sub in ("--help", "-h", "help"):
        cliver.output(click_help(skills, "/skills"))
        return

    if sub in _SUBCOMMANDS and wants_help(rest):
        cliver.output(click_help(_SUBCOMMANDS[sub], f"/skills {sub}"))
        return

    if sub == "list":
        _list_skills(cliver)
    elif sub == "show":
        if not rest:
            cliver.output(click_help(_SUBCOMMANDS["show"], "/skills show"))
            return
        _show_skill(cliver, rest.strip())
    elif sub == "run":
        if not rest:
            cliver.output(click_help(_SUBCOMMANDS["run"], "/skills run"))
            return
        run_parts = rest.split()
        skill_name = run_parts[0]
        prompt_file = None
        prompt_parts = []
        i = 1
        while i < len(run_parts):
            if run_parts[i] in ("-f", "--prompt-file"):
                if i + 1 < len(run_parts):
                    prompt_file = run_parts[i + 1]
                    i += 2
                else:
                    cliver.output("Missing file path after -f/--prompt-file")
                    return
            else:
                prompt_parts.append(run_parts[i])
                i += 1
        message = _resolve_prompt(cliver, " ".join(prompt_parts), prompt_file)
        if message is None:
            return
        _run_skill(cliver, skill_name, message)
    elif sub == "create":
        if not rest:
            cliver.output(click_help(_SUBCOMMANDS["create"], "/skills create"))
            return
        create_parts = rest.split(None, 1)
        skill_name = create_parts[0]
        desc = create_parts[1] if len(create_parts) > 1 else ""
        if not desc:
            cliver.output("Missing description")
            return
        save_global = "--global" in desc
        if save_global:
            desc = desc.replace("--global", "").strip()
        _create_skill(cliver, skill_name, desc, save_global)
    elif sub == "edit":
        if not rest:
            cliver.output("Usage: /skills edit <name>")
            return
        _edit_skill(cliver, rest.strip())
    elif sub == "update":
        if not rest:
            cliver.output(click_help(_SUBCOMMANDS["update"], "/skills update"))
            return
        update_parts = rest.split(None, 1)
        skill_name = update_parts[0]
        instructions = update_parts[1] if len(update_parts) > 1 else ""
        if not instructions:
            cliver.output("Missing update instructions")
            return
        _update_skill(cliver, skill_name, instructions)
    else:
        cliver.output(f"Unknown subcommand: /skills {sub}")
        cliver.output("Run '/skills help' for usage.")


# ---------------------------------------------------------------------------
# Click commands (thin wrappers)
# ---------------------------------------------------------------------------


@skills.command(name="list", help="List all discovered skills with name, description, and source location")
@pass_cliver
def list_skills(cliver: Cliver):
    """List all discovered skills with their source locations."""
    _list_skills(cliver)


@skills.command(name="run", help="Activate a skill and run it through LLM inference with optional initial prompt")
@click.argument("name", type=str)
@click.argument("message", nargs=-1)
@click.option(
    "-f",
    "--prompt-file",
    type=click.Path(exists=True),
    default=None,
    help="Read the prompt from a file",
)
@pass_cliver
def run_skill(cliver: Cliver, name: str, message: tuple, prompt_file: str | None):
    """Activate a skill and run it through LLM inference.

    NAME is the skill name to activate.
    MESSAGE (optional) is the initial prompt to send along with the skill.
    """
    inline = " ".join(message) if message else ""
    user_message = _resolve_prompt(cliver, inline, prompt_file)
    if user_message is None:
        return
    _run_skill(cliver, name, user_message)


@skills.command(name="create", help="Generate a new SKILL.md file using the LLM from a name and description")
@click.argument("name", type=str)
@click.argument("description", nargs=-1, required=True)
@click.option(
    "--global",
    "save_global",
    is_flag=True,
    default=False,
    help="Save to global skills directory (~/.cliver/skills/) instead of project-local .cliver/skills/",
)
@pass_cliver
def create_skill(cliver: Cliver, name: str, description: tuple, save_global: bool):
    """Create a new skill by asking the LLM to generate a SKILL.md file.

    NAME is the skill name (lowercase, hyphens allowed).
    DESCRIPTION is what kind of skill it is (remaining arguments joined).
    """
    desc_text = " ".join(description)
    _create_skill(cliver, name, desc_text, save_global)


@skills.command(name="show", help="Display the full SKILL.md content, metadata, and source location of a skill")
@click.argument("name", type=str)
@pass_cliver
def show_skill(cliver: Cliver, name: str):
    """Display the full content of a skill."""
    _show_skill(cliver, name)


@skills.command(name="update", help="Improve an existing SKILL.md using the LLM with natural-language instructions")
@click.argument("name", type=str)
@click.argument("instructions", nargs=-1, required=True)
@pass_cliver
def update_skill(cliver: Cliver, name: str, instructions: tuple):
    """Update an existing skill by asking the LLM to improve it.

    NAME is the skill name to update.
    INSTRUCTIONS describe how to improve the skill (remaining arguments joined).
    """
    improvement_text = " ".join(instructions)
    _update_skill(cliver, name, improvement_text)


@skills.command(name="edit", help="Open a skill's SKILL.md in $EDITOR for direct editing")
@click.argument("name", type=str)
@pass_cliver
def edit_skill(cliver: Cliver, name: str):
    _edit_skill(cliver, name)


# Populate subcommand map for dispatch help generation.
_SUBCOMMANDS.update(
    {
        "list": list_skills,
        "run": run_skill,
        "show": show_skill,
        "create": create_skill,
        "update": update_skill,
        "edit": edit_skill,
    }
)


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


def _list_skills(cliver: Cliver) -> None:
    """Display all discovered skills in a Rich table."""
    manager = SkillManager()
    all_skills = manager.list_skills()

    if not all_skills:
        cliver.output("No skills found.")
        cliver.output("Skills are discovered from:")
        cliver.output("  - .cliver/skills/ (project)")
        cliver.output(f"  - {get_config_dir() / 'skills'} (global)")
        cliver.output("  - ~/.agents/skills/ (agent-agnostic)")
        cliver.output("Create one with: /skills create <name> <description>")
        return

    table = Table(title=f"Discovered Skills ({len(all_skills)})", box=box.ROUNDED)
    table.add_column("Name", style="green", no_wrap=True)
    table.add_column("Description", style="white", max_width=60)
    table.add_column("Source", style="cyan", no_wrap=True)

    for s in all_skills:
        desc = s.description
        if len(desc) > 60:
            desc = desc[:57] + "..."
        table.add_row(s.name, desc, s.source)

    cliver.output(table)


def _suggest_available(cliver: Cliver, manager: SkillManager) -> None:
    """Show available skill names as a hint."""
    names = manager.get_skill_names()
    if names:
        cliver.output(f"Available skills: {', '.join(names)}")


async def _no_tools(_user_input, _tools):
    """filter_tools callback that disables all tools."""
    return []


def _llm_generate(cliver: Cliver, prompt: str, model: str, status_msg: str) -> str | None:
    """Call LLM to generate content (no tools, no streaming), with spinner and token display.

    Returns extracted SKILL.md content, or None on failure.
    """
    from cliver.cli_llm_call import LLMCallOptions, llm_call

    cliver.output(f"{status_msg}")
    result = llm_call(
        cliver,
        LLMCallOptions(
            user_input=prompt,
            model=model,
            stream=False,
        ),
    )

    if not result.success or not result.text:
        if not result.error:
            cliver.output("LLM returned no content.")
        return None

    return _extract_skill_content(result.text.strip())


_SKILL_PROMPT_RULES = "IMPORTANT: Do NOT call any skill toolsRespond with ONLY the SKILL.md file content directly. "


def _build_create_prompt(name: str, description: str) -> str:
    """Build the prompt for the LLM to generate a SKILL.md file."""
    return (
        f"Generate a SKILL.md file for a skill named '{name}'.\n"
        f"The skill is about: {description}\n\n"
        f"Requirements:\n"
        f"1. Start with YAML frontmatter between --- markers\n"
        f"2. The frontmatter MUST include:\n"
        f"   - name: {name}\n"
        f"   - description: A clear, concise description (under 200 chars)\n"
        f"3. Optionally include in frontmatter:\n"
        f"   - allowed-tools: space-delimited tool names if the skill needs specific tools\n"
        f"4. The body (after frontmatter) should contain:\n"
        f"   - Clear instructions for the LLM when this skill is activated\n"
        f"   - Step-by-step guidance or patterns to follow\n"
        f"   - Any relevant context or constraints\n\n"
        f"{_SKILL_PROMPT_RULES}"
    )


def _build_update_prompt(name: str, current_content: str, instructions: str) -> str:
    """Build the prompt for the LLM to update/improve a SKILL.md file."""
    return (
        f"Update the following SKILL.md file for skill '{name}'.\n\n"
        f"Current content:\n"
        f"```\n{current_content}\n```\n\n"
        f"Improvement instructions: {instructions}\n\n"
        f"Requirements:\n"
        f"1. Keep the same YAML frontmatter format (--- markers)\n"
        f"2. The frontmatter MUST keep name: {name}\n"
        f"3. Apply the improvement instructions to the skill content\n"
        f"4. Preserve any existing good content that isn't contradicted by the instructions\n\n"
        f"{_SKILL_PROMPT_RULES}"
    )


def _extract_skill_content(content: str) -> str:
    """Extract SKILL.md content, stripping any code fences the LLM may add."""
    # Strip markdown code blocks if present
    if content.startswith("```"):
        lines = content.split("\n")
        # Remove first line (```markdown or ```)
        lines = lines[1:]
        # Remove last line if it's ```
        if lines and lines[-1].strip() == "```":
            lines = lines[:-1]
        content = "\n".join(lines)

    # Ensure it starts with ---
    content = content.strip()
    if not content.startswith("---"):
        content = "---\n" + content

    return content
