"""
MundiX Skill Loader — Parses .md skill files into structured, executable instructions.

This module reads the 23 pentest skill files from the skills/ directory,
extracts YAML frontmatter (metadata), Markdown sections (objective, commands,
analysis, next steps, error handling), and code blocks (bash commands).

It provides:
- SkillMetadata: parsed YAML frontmatter (name, phase, tools, dependencies, mitre)
- SkillCommand: individual executable command with context and description
- SkillSection: a named section of the skill (objective, analysis, etc.)
- Skill: the full parsed skill object with all sections and commands
- SkillRegistry: the central registry that loads, indexes, and queries all skills

Usage:
    from skill_loader import SkillRegistry
    registry = SkillRegistry("/opt/mundix-cli/skills")
    registry.load_all()

    # Get a specific skill
    skill = registry.get("network_port_scan")

    # Get all skills for a phase
    recon_skills = registry.get_by_phase("1_reconnaissance")

    # Find skills that use a specific tool
    nmap_skills = registry.get_by_tool("nmap")

    # Get the execution plan for a full pentest
    plan = registry.get_pentest_plan()

    # Generate the AI system prompt for a skill
    prompt = skill.to_agent_prompt(target="example.com", workspace="/tmp/pentest")
"""

import os
import re
import yaml
import json
import hashlib
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple
from pathlib import Path


# ---------------------------------------------------------------------------
# Data Classes
# ---------------------------------------------------------------------------

@dataclass
class SkillCommand:
    """A single executable command extracted from a skill file."""
    raw: str                    # The raw command string with {placeholders}
    description: str            # Human-readable description of what this command does
    step_number: int            # Sequential step number within the skill
    step_title: str             # Title of the step (e.g., "Subfinder (Passivo - Rápido)")
    placeholders: List[str]     # List of placeholder names (e.g., ["target_domain", "workspace"])

    def render(self, variables: Dict[str, str]) -> str:
        """Replace placeholders with actual values."""
        rendered = self.raw
        for key, value in variables.items():
            rendered = rendered.replace(f"{{{key}}}", value)
        # Check for unresolved placeholders
        remaining = re.findall(r'\{(\w+)\}', rendered)
        if remaining:
            raise ValueError(f"Unresolved placeholders: {remaining}")
        return rendered


@dataclass
class SkillSection:
    """A named section of a skill (e.g., Objetivo, Análise de Resultados)."""
    title: str                  # Section heading (e.g., "Objetivo")
    content: str                # Full markdown content of the section
    key: str                    # Normalized key (e.g., "objetivo", "analise_de_resultados")


@dataclass
class SkillMetadata:
    """Parsed YAML frontmatter from a skill file."""
    name: str                           # Skill identifier (e.g., "osint_domain_enumeration")
    phase: str                          # Phase identifier (e.g., "1_reconnaissance")
    phase_number: int                   # Numeric phase (e.g., 1)
    phase_name: str                     # Phase name (e.g., "reconnaissance")
    tools: List[str]                    # Required tools (e.g., ["subfinder", "amass"])
    dependencies: List[str]             # Pre-conditions (e.g., ["target_domain_identified"])
    mitre_tactics: List[Dict[str, str]] # MITRE ATT&CK tactics (e.g., [{"id": "TA0043", "name": "Reconnaissance"}])


@dataclass
class Skill:
    """A fully parsed pentest skill with metadata, sections, and commands."""
    metadata: SkillMetadata
    sections: Dict[str, SkillSection]   # key -> SkillSection
    commands: List[SkillCommand]        # All executable commands in order
    raw_content: str                    # Original .md file content
    file_path: str                      # Path to the .md file
    checksum: str                       # MD5 hash for change detection

    @property
    def name(self) -> str:
        return self.metadata.name

    @property
    def phase(self) -> str:
        return self.metadata.phase

    @property
    def phase_number(self) -> int:
        return self.metadata.phase_number

    @property
    def tools(self) -> List[str]:
        return self.metadata.tools

    @property
    def objective(self) -> str:
        """Get the skill's objective text."""
        sec = self.sections.get("objetivo")
        return sec.content if sec else ""

    @property
    def conditions(self) -> str:
        """Get the execution conditions."""
        sec = self.sections.get("condicoes_de_execucao")
        return sec.content if sec else ""

    @property
    def analysis_guide(self) -> str:
        """Get the results analysis guide."""
        for key in ("analise_de_resultados", "analise_de_resultados_como_interpretar"):
            sec = self.sections.get(key)
            if sec:
                return sec.content
        return ""

    @property
    def next_steps(self) -> str:
        """Get the next steps / agent decision guide."""
        for key in ("proximos_passos", "proximos_passos_decisao_do_agente"):
            sec = self.sections.get(key)
            if sec:
                return sec.content
        return ""

    @property
    def error_handling(self) -> str:
        """Get the error handling guide."""
        sec = self.sections.get("tratamento_de_erros")
        return sec.content if sec else ""

    def get_all_placeholders(self) -> List[str]:
        """Get all unique placeholders across all commands."""
        placeholders = set()
        for cmd in self.commands:
            placeholders.update(cmd.placeholders)
        return sorted(placeholders)

    def to_agent_prompt(self, variables: Optional[Dict[str, str]] = None) -> str:
        """
        Generate a structured prompt that the AI agent can use to execute this skill.
        Variables like {target_domain}, {workspace} are replaced if provided.
        """
        variables = variables or {}
        lines = []

        # Header
        lines.append(f"# SKILL: {self.metadata.name}")
        lines.append(f"**Phase:** {self.metadata.phase}")
        lines.append(f"**Tools:** {', '.join(self.metadata.tools)}")
        lines.append(f"**MITRE:** {', '.join(t['id'] + ' ' + t['name'] for t in self.metadata.mitre_tactics)}")
        lines.append("")

        # Objective
        lines.append(f"## Objetivo")
        lines.append(self.objective)
        lines.append("")

        # Conditions
        if self.conditions:
            lines.append(f"## Condições de Execução")
            lines.append(self.conditions)
            lines.append("")

        # Commands
        lines.append(f"## Comandos para Executar")
        for cmd in self.commands:
            lines.append(f"### Passo {cmd.step_number}: {cmd.step_title}")
            lines.append(cmd.description)
            try:
                rendered = cmd.render(variables) if variables else cmd.raw
            except ValueError:
                rendered = cmd.raw
            lines.append(f"```bash\n{rendered}\n```")
            lines.append("")

        # Analysis
        if self.analysis_guide:
            lines.append(f"## Como Analisar os Resultados")
            lines.append(self.analysis_guide)
            lines.append("")

        # Next Steps
        if self.next_steps:
            lines.append(f"## Próximos Passos (Decisão)")
            lines.append(self.next_steps)
            lines.append("")

        # Error Handling
        if self.error_handling:
            lines.append(f"## Tratamento de Erros")
            lines.append(self.error_handling)
            lines.append("")

        return "\n".join(lines)

    def to_dict(self) -> Dict:
        """Serialize the skill to a dictionary (for JSON export or API)."""
        return {
            "name": self.metadata.name,
            "phase": self.metadata.phase,
            "phase_number": self.metadata.phase_number,
            "tools": self.metadata.tools,
            "dependencies": self.metadata.dependencies,
            "mitre_tactics": self.metadata.mitre_tactics,
            "objective": self.objective,
            "command_count": len(self.commands),
            "placeholders": self.get_all_placeholders(),
            "commands": [
                {
                    "step": cmd.step_number,
                    "title": cmd.step_title,
                    "description": cmd.description,
                    "raw": cmd.raw,
                    "placeholders": cmd.placeholders,
                }
                for cmd in self.commands
            ],
            "sections": list(self.sections.keys()),
            "checksum": self.checksum,
        }


# ---------------------------------------------------------------------------
# Parser
# ---------------------------------------------------------------------------

class SkillParser:
    """Parses a single .md skill file into a Skill object."""

    # Regex to extract YAML frontmatter between --- delimiters
    FRONTMATTER_RE = re.compile(r'^---\s*\n(.*?)\n---\s*\n', re.DOTALL)

    # Regex to extract code blocks
    CODE_BLOCK_RE = re.compile(r'```(?:bash|sh|cmd|powershell)?\s*\n(.*?)```', re.DOTALL)

    # Regex to find placeholders like {target_domain}
    PLACEHOLDER_RE = re.compile(r'\{(\w+)\}')

    # Regex to find H2 headings (## Title)
    H2_RE = re.compile(r'^##\s+(.+)$', re.MULTILINE)

    # Regex to find H1 headings (# Title)
    H1_RE = re.compile(r'^#\s+(.+)$', re.MULTILINE)

    @staticmethod
    def _normalize_key(title: str) -> str:
        """Normalize a section title to a dict key."""
        # Remove parenthetical content
        key = re.sub(r'\(.*?\)', '', title)
        # Remove special chars, lowercase, replace spaces with underscores
        key = re.sub(r'[^\w\s]', '', key)
        key = key.strip().lower().replace(' ', '_')
        # Remove accents (simple approach)
        replacements = {
            'á': 'a', 'à': 'a', 'ã': 'a', 'â': 'a',
            'é': 'e', 'ê': 'e',
            'í': 'i',
            'ó': 'o', 'ô': 'o', 'õ': 'o',
            'ú': 'u', 'ü': 'u',
            'ç': 'c',
        }
        for orig, repl in replacements.items():
            key = key.replace(orig, repl)
        return key

    def parse_frontmatter(self, content: str) -> Tuple[Dict, str]:
        """Extract and parse YAML frontmatter, return (metadata_dict, remaining_content)."""
        match = self.FRONTMATTER_RE.match(content)
        if not match:
            return {}, content

        yaml_str = match.group(1)
        remaining = content[match.end():]

        try:
            metadata = yaml.safe_load(yaml_str)
        except yaml.YAMLError:
            metadata = {}

        return metadata or {}, remaining

    def parse_metadata(self, raw_meta: Dict) -> SkillMetadata:
        """Convert raw YAML dict to SkillMetadata."""
        name = raw_meta.get("name", "unknown")
        phase = raw_meta.get("phase", "0_unknown")

        # Parse phase number and name
        phase_parts = phase.split("_", 1)
        phase_number = int(phase_parts[0]) if phase_parts[0].isdigit() else 0
        phase_name = phase_parts[1] if len(phase_parts) > 1 else phase

        # Parse tools
        tools = raw_meta.get("tools", [])
        if isinstance(tools, str):
            tools = [t.strip() for t in tools.split(",")]

        # Parse dependencies
        deps = raw_meta.get("dependencies", [])
        if isinstance(deps, str):
            deps = [d.strip() for d in deps.split(",")]

        # Parse MITRE tactics
        mitre_raw = raw_meta.get("mitre_tactic", "")
        mitre_tactics = []
        if mitre_raw:
            if isinstance(mitre_raw, str):
                # Can be "TA0043 (Reconnaissance)" or "TA0001 (Initial Access), TA0006 (Credential Access)"
                for part in mitre_raw.split("),"):
                    part = part.strip()
                    if not part.endswith(")"):
                        part += ")"
                    m = re.match(r'(TA\d+)\s*\((.+?)\)', part)
                    if m:
                        mitre_tactics.append({"id": m.group(1), "name": m.group(2)})

        return SkillMetadata(
            name=name,
            phase=phase,
            phase_number=phase_number,
            phase_name=phase_name,
            tools=tools,
            dependencies=deps,
            mitre_tactics=mitre_tactics,
        )

    def parse_sections(self, content: str) -> Dict[str, SkillSection]:
        """Parse all H1 sections from the markdown content."""
        sections = {}

        # Split by H1 headings
        parts = self.H1_RE.split(content)

        # parts[0] is content before first H1 (usually empty)
        # parts[1] is first H1 title, parts[2] is its content, etc.
        for i in range(1, len(parts), 2):
            if i + 1 < len(parts):
                title = parts[i].strip()
                body = parts[i + 1].strip()
                key = self._normalize_key(title)
                sections[key] = SkillSection(title=title, content=body, key=key)

        return sections

    def parse_commands(self, content: str) -> List[SkillCommand]:
        """Extract all executable commands from code blocks with context."""
        commands = []
        step_number = 0

        # Find all H2 sections that contain code blocks
        h2_positions = [(m.start(), m.group(1)) for m in self.H2_RE.finditer(content)]

        # For each code block, find which H2 section it belongs to
        for code_match in self.CODE_BLOCK_RE.finditer(content):
            code_start = code_match.start()
            code_content = code_match.group(1).strip()

            # Skip empty code blocks
            if not code_content:
                continue

            # Find the H2 section this code block belongs to
            step_title = "Command"
            for pos, title in reversed(h2_positions):
                if pos < code_start:
                    step_title = title
                    break

            # Get description: text between H2 heading and code block
            description = ""
            for pos, title in reversed(h2_positions):
                if pos < code_start:
                    section_text = content[pos:code_start]
                    # Remove the H2 heading itself
                    section_text = re.sub(r'^##\s+.+\n', '', section_text).strip()
                    # Remove other code blocks from description
                    section_text = self.CODE_BLOCK_RE.sub('', section_text).strip()
                    description = section_text
                    break

            # Split multi-line commands (each line is a separate command)
            for line in code_content.split('\n'):
                line = line.strip()
                # Skip comments and empty lines
                if not line or line.startswith('#'):
                    continue

                step_number += 1
                placeholders = self.PLACEHOLDER_RE.findall(line)

                commands.append(SkillCommand(
                    raw=line,
                    description=description,
                    step_number=step_number,
                    step_title=step_title,
                    placeholders=placeholders,
                ))

        return commands

    def parse_file(self, file_path: str) -> Skill:
        """Parse a complete .md skill file into a Skill object."""
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()

        checksum = hashlib.md5(content.encode()).hexdigest()

        # Parse frontmatter
        raw_meta, body = self.parse_frontmatter(content)
        metadata = self.parse_metadata(raw_meta)

        # Parse sections
        sections = self.parse_sections(body)

        # Parse commands
        commands = self.parse_commands(body)

        return Skill(
            metadata=metadata,
            sections=sections,
            commands=commands,
            raw_content=content,
            file_path=file_path,
            checksum=checksum,
        )


# ---------------------------------------------------------------------------
# Registry
# ---------------------------------------------------------------------------

PHASE_ORDER = [
    "1_reconnaissance",
    "2_scanning_enumeration",
    "3_vulnerability_analysis",
    "4_exploitation",
    "5_post_exploitation",
    "6_reporting",
]

PHASE_DESCRIPTIONS = {
    "1_reconnaissance": "Reconhecimento passivo e ativo — descobrir subdomínios, e-mails, tecnologias e vazamentos.",
    "2_scanning_enumeration": "Varredura de portas, diretórios, serviços e protocolos para mapear a superfície de ataque.",
    "3_vulnerability_analysis": "Identificação de vulnerabilidades via CVE search, scanners automatizados e testes manuais OWASP.",
    "4_exploitation": "Exploração ativa das vulnerabilidades para obter acesso (SQLi, brute force, exploits, upload).",
    "5_post_exploitation": "Escalação de privilégios, extração de hashes, movimentação lateral após acesso inicial.",
    "6_reporting": "Geração do relatório final com sumário executivo, detalhes técnicos e mapeamento MITRE ATT&CK.",
}


class SkillRegistry:
    """
    Central registry that loads, indexes, and queries all pentest skills.

    This is the main entry point for the Agent Orchestrator to interact with skills.
    """

    def __init__(self, skills_dir: str):
        self.skills_dir = skills_dir
        self.parser = SkillParser()
        self._skills: Dict[str, Skill] = {}           # name -> Skill
        self._by_phase: Dict[str, List[Skill]] = {}    # phase -> [Skill]
        self._by_tool: Dict[str, List[Skill]] = {}     # tool -> [Skill]
        self._loaded = False

    def load_all(self) -> int:
        """
        Load all .md skill files from the skills directory.
        Returns the number of skills loaded.
        """
        self._skills.clear()
        self._by_phase.clear()
        self._by_tool.clear()

        skills_path = Path(self.skills_dir)
        if not skills_path.exists():
            raise FileNotFoundError(f"Skills directory not found: {self.skills_dir}")

        count = 0
        for md_file in sorted(skills_path.glob("*.md")):
            try:
                skill = self.parser.parse_file(str(md_file))
                self._register(skill)
                count += 1
            except Exception as e:
                print(f"[SkillLoader] WARNING: Failed to parse {md_file.name}: {e}")

        self._loaded = True
        return count

    def reload(self) -> int:
        """Reload all skills (useful for hot-reload after skill updates)."""
        return self.load_all()

    def reload_if_changed(self) -> int:
        """
        Check for file changes and reload only modified skills.
        Returns the number of skills that were reloaded.
        """
        reloaded = 0
        skills_path = Path(self.skills_dir)

        for md_file in sorted(skills_path.glob("*.md")):
            with open(md_file, 'r', encoding='utf-8') as f:
                new_checksum = hashlib.md5(f.read().encode()).hexdigest()

            # Check if this skill exists and has changed
            existing = None
            for skill in self._skills.values():
                if skill.file_path == str(md_file):
                    existing = skill
                    break

            if existing is None or existing.checksum != new_checksum:
                try:
                    skill = self.parser.parse_file(str(md_file))
                    # Remove old version if exists
                    if existing:
                        self._unregister(existing)
                    self._register(skill)
                    reloaded += 1
                except Exception as e:
                    print(f"[SkillLoader] WARNING: Failed to reload {md_file.name}: {e}")

        return reloaded

    def _register(self, skill: Skill):
        """Register a skill in all indexes."""
        self._skills[skill.name] = skill

        # Index by phase
        if skill.phase not in self._by_phase:
            self._by_phase[skill.phase] = []
        self._by_phase[skill.phase].append(skill)

        # Index by tool
        for tool in skill.tools:
            tool_lower = tool.lower()
            if tool_lower not in self._by_tool:
                self._by_tool[tool_lower] = []
            self._by_tool[tool_lower].append(skill)

    def _unregister(self, skill: Skill):
        """Remove a skill from all indexes."""
        self._skills.pop(skill.name, None)

        if skill.phase in self._by_phase:
            self._by_phase[skill.phase] = [
                s for s in self._by_phase[skill.phase] if s.name != skill.name
            ]

        for tool in skill.tools:
            tool_lower = tool.lower()
            if tool_lower in self._by_tool:
                self._by_tool[tool_lower] = [
                    s for s in self._by_tool[tool_lower] if s.name != skill.name
                ]

    # --- Query Methods ---

    def get(self, name: str) -> Optional[Skill]:
        """Get a skill by name."""
        return self._skills.get(name)

    def get_all(self) -> List[Skill]:
        """Get all loaded skills, sorted by phase."""
        return sorted(self._skills.values(), key=lambda s: (s.phase_number, s.name))

    def get_by_phase(self, phase: str) -> List[Skill]:
        """Get all skills for a given phase (e.g., '1_reconnaissance')."""
        return self._by_phase.get(phase, [])

    def get_by_tool(self, tool: str) -> List[Skill]:
        """Get all skills that use a specific tool (e.g., 'nmap')."""
        return self._by_tool.get(tool.lower(), [])

    def get_by_dependency(self, dependency: str) -> List[Skill]:
        """Get all skills that require a specific dependency."""
        return [s for s in self._skills.values() if dependency in s.metadata.dependencies]

    def search(self, query: str) -> List[Skill]:
        """Search skills by keyword in name, objective, or tools."""
        query_lower = query.lower()
        results = []
        for skill in self._skills.values():
            if (query_lower in skill.name.lower() or
                query_lower in skill.objective.lower() or
                any(query_lower in t.lower() for t in skill.tools)):
                results.append(skill)
        return sorted(results, key=lambda s: s.phase_number)

    def get_phases(self) -> List[Dict]:
        """Get all phases with their skills, in order."""
        phases = []
        for phase_id in PHASE_ORDER:
            skills = self.get_by_phase(phase_id)
            phases.append({
                "id": phase_id,
                "number": int(phase_id.split("_")[0]),
                "name": phase_id.split("_", 1)[1],
                "description": PHASE_DESCRIPTIONS.get(phase_id, ""),
                "skill_count": len(skills),
                "skills": [s.name for s in skills],
            })
        return phases

    def get_pentest_plan(self) -> List[Dict]:
        """
        Generate a full pentest execution plan — the ordered sequence of
        all skills across all phases that the Agent Orchestrator should follow.
        """
        plan = []
        step = 0
        for phase_id in PHASE_ORDER:
            skills = self.get_by_phase(phase_id)
            for skill in skills:
                step += 1
                plan.append({
                    "step": step,
                    "phase": phase_id,
                    "skill": skill.name,
                    "objective": skill.objective.split('\n')[0][:120],  # First line, truncated
                    "tools": skill.tools,
                    "dependencies": skill.metadata.dependencies,
                    "command_count": len(skill.commands),
                    "placeholders": skill.get_all_placeholders(),
                })
        return plan

    def get_next_skills(self, completed_skills: List[str], findings: Dict[str, bool]) -> List[Skill]:
        """
        Determine which skills should be executed next based on what's been
        completed and what findings/conditions have been met.

        Args:
            completed_skills: List of skill names already executed.
            findings: Dict of condition_name -> True/False (e.g., {"sqli_vulnerability_identified": True})

        Returns:
            List of skills whose dependencies are satisfied and haven't been run yet.
        """
        available = []
        for skill in self.get_all():
            # Skip already completed
            if skill.name in completed_skills:
                continue

            # Check if all dependencies are satisfied
            deps_met = True
            for dep in skill.metadata.dependencies:
                if dep not in findings or not findings[dep]:
                    deps_met = False
                    break

            if deps_met:
                available.append(skill)

        return available

    def get_stats(self) -> Dict:
        """Get statistics about the loaded skills."""
        total_commands = sum(len(s.commands) for s in self._skills.values())
        all_tools = set()
        all_placeholders = set()
        for skill in self._skills.values():
            all_tools.update(skill.tools)
            all_placeholders.update(skill.get_all_placeholders())

        return {
            "total_skills": len(self._skills),
            "total_commands": total_commands,
            "total_tools": len(all_tools),
            "total_placeholders": len(all_placeholders),
            "tools": sorted(all_tools),
            "placeholders": sorted(all_placeholders),
            "phases": {
                phase: len(self.get_by_phase(phase))
                for phase in PHASE_ORDER
            },
        }

    def export_catalog(self, output_path: str, format: str = "json"):
        """Export the full skill catalog to JSON or Markdown."""
        if format == "json":
            catalog = {
                "version": "1.0",
                "total_skills": len(self._skills),
                "phases": self.get_phases(),
                "skills": [s.to_dict() for s in self.get_all()],
                "stats": self.get_stats(),
            }
            with open(output_path, 'w', encoding='utf-8') as f:
                json.dump(catalog, f, indent=2, ensure_ascii=False)

        elif format == "markdown":
            lines = ["# MundiX Skill Catalog\n"]
            stats = self.get_stats()
            lines.append(f"**Total Skills:** {stats['total_skills']} | "
                         f"**Total Commands:** {stats['total_commands']} | "
                         f"**Total Tools:** {stats['total_tools']}\n")

            for phase_info in self.get_phases():
                lines.append(f"\n## Phase {phase_info['number']}: {phase_info['name'].replace('_', ' ').title()}")
                lines.append(f"_{phase_info['description']}_\n")
                lines.append("| Skill | Tools | Commands | Placeholders |")
                lines.append("|---|---|---|---|")
                for skill_name in phase_info['skills']:
                    skill = self.get(skill_name)
                    if skill:
                        lines.append(
                            f"| `{skill.name}` | {', '.join(skill.tools)} | "
                            f"{len(skill.commands)} | {', '.join(skill.get_all_placeholders())} |"
                        )
                lines.append("")

            with open(output_path, 'w', encoding='utf-8') as f:
                f.write('\n'.join(lines))


# ---------------------------------------------------------------------------
# CLI Entry Point (for testing)
# ---------------------------------------------------------------------------

def main():
    """CLI entry point for testing the skill loader."""
    import sys

    skills_dir = sys.argv[1] if len(sys.argv) > 1 else "/opt/mundix-cli/skills"

    print(f"[SkillLoader] Loading skills from: {skills_dir}")
    registry = SkillRegistry(skills_dir)
    count = registry.load_all()
    print(f"[SkillLoader] Loaded {count} skills\n")

    # Print stats
    stats = registry.get_stats()
    print(f"  Total commands: {stats['total_commands']}")
    print(f"  Total tools:    {stats['total_tools']}")
    print(f"  Tools:          {', '.join(stats['tools'])}")
    print()

    # Print phases
    for phase in registry.get_phases():
        print(f"  Phase {phase['number']}: {phase['name']} ({phase['skill_count']} skills)")
        for skill_name in phase['skills']:
            skill = registry.get(skill_name)
            if skill:
                print(f"    - {skill.name} [{len(skill.commands)} cmds] "
                      f"tools={skill.tools}")
    print()

    # Print pentest plan
    print("  === Full Pentest Plan ===")
    for step in registry.get_pentest_plan():
        print(f"  Step {step['step']:2d}: [{step['phase']}] {step['skill']} "
              f"({step['command_count']} cmds)")

    # Test agent prompt generation
    if count > 0:
        first_skill = registry.get_all()[0]
        print(f"\n  === Agent Prompt for '{first_skill.name}' ===")
        prompt = first_skill.to_agent_prompt({"target_domain": "example.com", "workspace": "/tmp/pentest"})
        print(prompt[:500] + "..." if len(prompt) > 500 else prompt)


if __name__ == "__main__":
    main()
