"""CLI Shell Schema Generator — extracts command structure from proxym CLI.

Supports multiple schema generation methods:
1. Dynamic from code (click introspection) - default
2. From cached JSON file (fastest)
3. From API endpoint (when server is running)
4. Shell-based generation (subprocess proxym --help)
"""
from __future__ import annotations

import json
import re
import subprocess
import time
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any

import httpx

# Cache file path
SCHEMA_CACHE_PATH = Path.home() / ".config" / "proxym" / "cli_schema.json"


@dataclass
class CLICommand:
    """Represents a CLI command."""
    name: str
    description: str
    arguments: list[dict[str, Any]]
    options: list[dict[str, Any]]
    is_group: bool = False
    subcommands: list["CLICommand"] | None = None


def extract_click_params(func) -> tuple[list[dict], list[dict]]:
    """Extract arguments and options from click-decorated function."""
    args = []
    opts = []

    if not hasattr(func, '__click_params__'):
        return args, opts

    for param in getattr(func, '__click_params__', []):
        param_info = {
            "name": param.name,
            "type": str(param.type).replace('click.types.', ''),
            "required": param.required,
            "help": getattr(param, 'help', None),
            "default": param.default,
            "is_flag": getattr(param, 'is_flag', False),
        }

        if hasattr(param, 'opts') and param.opts:
            opts.append(param_info)
        else:
            args.append(param_info)

    return args, opts


def scan_command_group(group, parent_name: str = "") -> list[CLICommand]:
    """Recursively scan click command group and extract schema."""
    commands = []

    for name, cmd in getattr(group, 'commands', {}).items():
        full_name = f"{parent_name} {name}" if parent_name else name

        if isinstance(cmd, type) and hasattr(cmd, 'commands'):
            # It's a command group
            subcommands = scan_command_group(cmd, full_name)
            args, opts = extract_click_params(cmd.callback) if hasattr(cmd, 'callback') else ([], [])

            commands.append(CLICommand(
                name=full_name,
                description=cmd.help or cmd.short_help or "",
                arguments=args,
                options=opts,
                is_group=True,
                subcommands=subcommands
            ))
        else:
            # It's a command
            args, opts = extract_click_params(cmd.callback) if hasattr(cmd, 'callback') else ([], [])

            commands.append(CLICommand(
                name=full_name,
                description=cmd.help or cmd.short_help or "",
                arguments=args,
                options=opts,
                is_group=False
            ))

    return commands


def generate_cli_schema(cli_group) -> dict[str, Any]:
    """Generate complete CLI schema from click CLI group."""
    commands = scan_command_group(cli_group)

    return {
        "schema_version": "1.0",
        "cli_name": "proxym",
        "commands": [asdict(cmd) for cmd in commands],
        "usage_patterns": generate_usage_patterns(commands),
    }


def generate_usage_patterns(commands: list[CLICommand]) -> list[dict[str, str]]:
    """Generate common usage patterns from commands."""
    patterns = []

    for cmd in commands:
        if cmd.is_group and cmd.subcommands:
            for sub in cmd.subcommands:
                pattern = f"proxym {sub.name}"
                if sub.arguments:
                    pattern += " " + " ".join([f"<{a['name']}>" for a in sub.arguments])
                if sub.options:
                    pattern += " " + " ".join([f"[{o['name']}]" for o in sub.options])

                patterns.append({
                    "pattern": pattern,
                    "description": sub.description,
                    "example": generate_example(sub)
                })
        elif not cmd.is_group:
            pattern = f"proxym {cmd.name}"
            if cmd.arguments:
                pattern += " " + " ".join([f"<{a['name']}>" for a in cmd.arguments])
            if cmd.options:
                pattern += " " + " ".join([f"[{o['name']}]" for o in cmd.options])

            patterns.append({
                "pattern": pattern,
                "description": cmd.description,
                "example": generate_example(cmd)
            })

    return patterns


def generate_example(cmd: CLICommand) -> str:
    """Generate example usage for a command."""
    example = f"proxym {cmd.name}"

    for arg in cmd.arguments:
        if arg['name'] in ['project', 'ticket', 'vm', 'tool']:
            example += f" <{arg['name']}_id>"
        elif arg['type'] == 'STRING':
            example += ' "text"'
        else:
            example += f" {arg['name']}"

    for opt in cmd.options[:2]:  # Show first 2 options
        if opt['is_flag']:
            example += f" --{opt['name']}"

    return example


def _get_schema_via_shell() -> dict[str, Any] | None:
    """Generate schema by running 'proxym --help' and parsing output."""
    try:
        result = subprocess.run(
            ["proxym", "--help"],
            capture_output=True,
            text=True,
            timeout=5
        )
        if result.returncode != 0:
            return None

        lines = result.stdout.split('\n')
        commands = []
        usage_patterns = []

        # Parse help output for commands
        cmd_pattern = re.compile(r'^\s+([a-z-]+)\s+(.+)$')
        in_commands = False

        for line in lines:
            if 'Commands:' in line:
                in_commands = True
                continue
            if in_commands:
                if line.strip() == '' or line.startswith('  --'):
                    in_commands = False
                    continue
                match = cmd_pattern.match(line)
                if match:
                    cmd_name, desc = match.groups()
                    cmd_name = cmd_name.strip()
                    desc = desc.strip()
                    commands.append({
                        "name": cmd_name,
                        "description": desc,
                        "arguments": [],
                        "options": [],
                        "is_group": False,
                        "subcommands": None
                    })
                    usage_patterns.append({
                        "pattern": f"proxym {cmd_name}",
                        "description": desc,
                        "example": f"proxym {cmd_name}"
                    })

        return {
            "schema_version": "1.0",
            "cli_name": "proxym",
            "commands": commands,
            "usage_patterns": usage_patterns,
            "_source": "shell"
        }
    except Exception:
        return None


def _get_schema_via_api(proxy_url: str = "http://localhost:8080") -> dict[str, Any] | None:
    """Fetch schema from running proxym API server."""
    try:
        r = httpx.get(f"{proxy_url}/api/v1/cli/schema", timeout=5)
        if r.status_code == 200:
            schema = r.json()
            schema["_source"] = "api"
            return schema
    except Exception:
        pass
    return None


def _load_cached_schema() -> dict[str, Any] | None:
    """Load schema from cache file if it exists and is fresh (< 1 day old)."""
    try:
        if SCHEMA_CACHE_PATH.exists():
            mtime = SCHEMA_CACHE_PATH.stat().st_mtime
            age_hours = (time.time() - mtime) / 3600
            if age_hours < 24:  # Cache valid for 24 hours
                with open(SCHEMA_CACHE_PATH) as f:
                    schema = json.load(f)
                    schema["_source"] = "cache"
                    return schema
    except Exception:
        pass
    return None


def _save_cached_schema(schema: dict[str, Any]) -> None:
    """Save schema to cache file."""
    try:
        SCHEMA_CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
        with open(SCHEMA_CACHE_PATH, 'w') as f:
            json.dump(schema, f, indent=2, ensure_ascii=False)
    except Exception:
        pass


def generate_schema_multi_method(
    proxy_url: str = "http://localhost:8080",
    prefer_cache: bool = True
) -> dict[str, Any]:
    """Generate CLI schema using multiple methods, falling back as needed.
    
    Priority:
    1. Cache (if fresh and prefer_cache=True)
    2. API endpoint (if server running)
    3. Dynamic from code (if imports work)
    4. Shell-based (proxym --help)
    5. Hardcoded minimal fallback
    """
    # Try cache first
    if prefer_cache:
        cached = _load_cached_schema()
        if cached:
            return cached

    # Try API endpoint
    api_schema = _get_schema_via_api(proxy_url)
    if api_schema:
        _save_cached_schema(api_schema)
        return api_schema

    # Try dynamic from code
    try:
        from proxym.ctl import cli
        schema = generate_cli_schema(cli)
        schema["_source"] = "dynamic"
        _save_cached_schema(schema)
        return schema
    except Exception:
        pass

    # Try shell-based
    shell_schema = _get_schema_via_shell()
    if shell_schema:
        _save_cached_schema(shell_schema)
        return shell_schema

    # Ultimate fallback
    return _get_minimal_fallback_schema()


def _get_minimal_fallback_schema() -> dict[str, Any]:
    """Minimal hardcoded schema as last resort."""
    return {
        "schema_version": "1.0",
        "cli_name": "proxym",
        "commands": [],
        "usage_patterns": [
            {"pattern": "proxym status", "description": "Check server status", "example": "proxym status"},
            {"pattern": "proxym ticket list", "description": "List all tickets", "example": "proxym ticket list"},
            {"pattern": "proxym ticket add <description> -p <project> -t <tool>", "description": "Create ticket", "example": "proxym ticket add 'fix bug' -p myproject -t aider"},
            {"pattern": "proxym project list", "description": "List projects", "example": "proxym project list"},
            {"pattern": "proxym project add <path> --name <name>", "description": "Register project", "example": "proxym project add /path/to/project --name myproject"},
            {"pattern": "proxym board", "description": "Show kanban board", "example": "proxym board"},
            {"pattern": "proxym vm list", "description": "List VMs", "example": "proxym vm list"},
            {"pattern": "proxym sprint list", "description": "List sprints", "example": "proxym sprint list"},
            {"pattern": "proxym dispatch <ticket_id>", "description": "Dispatch ticket", "example": "proxym dispatch TKT-123"},
            {"pattern": "proxym analyze <ticket_id>", "description": "Analyze ticket", "example": "proxym analyze TKT-123"},
            {"pattern": "proxym q", "description": "Show job queue", "example": "proxym q"},
        ],
        "_source": "fallback"
    }


def save_schema_to_file(schema: dict[str, Any], path: Path) -> None:
    """Save CLI schema to JSON file."""
    with open(path, 'w') as f:
        json.dump(schema, f, indent=2, ensure_ascii=False)


def load_schema_from_file(path: Path) -> dict[str, Any]:
    """Load CLI schema from JSON file."""
    with open(path) as f:
        return json.load(f)


def format_schema_for_llm(schema: dict[str, Any]) -> str:
    """Format CLI schema as string for LLM prompt."""
    # Check if schema has error (fallback schema)
    if schema.get("_error"):
        return """# Proxym CLI Schema (Available Commands)

## Common Commands

- `proxym status` — Check server status
- `proxym ticket list` — List all tickets
- `proxym ticket add <description> -p <project> -t <tool>` — Create ticket
- `proxym project list` — List projects
- `proxym project add <path> --name <name>` — Register project
- `proxym board` — Show kanban board
- `proxym vm list` — List VMs
- `proxym tools list` — List available tools
- `proxym sprint list` — List sprints
- `proxym analyze <ticket_id>` — Analyze ticket
- `proxym q` — Show job queue
- `proxym jobs` — Show job history
"""

    lines = [
        "# Proxym CLI Schema",
        f"<!-- source: {schema.get('_source', 'unknown')} -->",
        "",
        "Available commands:",
        ""
    ]

    for cmd_data in schema.get('commands', []):
        _format_command(lines, cmd_data, 0)

    lines.extend([
        "",
        "## Common Usage Patterns",
        ""
    ])

    for pattern in schema.get('usage_patterns', [])[:20]:  # Limit to 20 patterns
        lines.append(f"- `{pattern['pattern']}`")
        lines.append(f"  Example: {pattern['example']}")
        lines.append(f"  {pattern['description']}")
        lines.append("")

    return "\n".join(lines)


def _format_command(lines: list, cmd_data: dict, indent: int) -> None:
    """Helper to format command data."""
    prefix = "  " * indent
    name = cmd_data.get('name', '')
    desc = cmd_data.get('description', '').split('\n')[0]  # First line only

    if cmd_data.get('is_group'):
        lines.append(f"{prefix}## {name} group")
        lines.append(f"{prefix}  {desc}")
        for sub in cmd_data.get('subcommands', []):
            _format_command(lines, sub, indent + 1)
    else:
        lines.append(f"{prefix}- `{name}`: {desc}")

        args = cmd_data.get('arguments', [])
        if args:
            lines.append(f"{prefix}  Arguments: {', '.join([a['name'] for a in args])}")

        opts = cmd_data.get('options', [])
        if opts:
            opt_names = [o['name'] for o in opts if not o.get('is_flag', False)][:3]
            if opt_names:
                lines.append(f"{prefix}  Options: {', '.join([f'--{o}' for o in opt_names])}")


# Singleton for CLI schema with multi-method generation
_cli_schema_cache: dict[str, Any] | None = None


def get_cli_schema(
    force_refresh: bool = False,
    proxy_url: str = "http://localhost:8080",
    prefer_cache: bool = True
) -> dict[str, Any]:
    """Get CLI schema using multiple generation methods with caching.
    
    Args:
        force_refresh: Ignore cache and regenerate
        proxy_url: URL for API-based schema generation
        prefer_cache: Use cached schema if available
    """
    global _cli_schema_cache

    if _cli_schema_cache is not None and not force_refresh:
        return _cli_schema_cache

    schema = generate_schema_multi_method(proxy_url, prefer_cache)
    _cli_schema_cache = schema
    return schema


def refresh_cli_schema(proxy_url: str = "http://localhost:8080") -> dict[str, Any]:
    """Force refresh the CLI schema cache."""
    global _cli_schema_cache
    
    # Clear cache file
    if SCHEMA_CACHE_PATH.exists():
        SCHEMA_CACHE_PATH.unlink()
    
    _cli_schema_cache = None
    return get_cli_schema(force_refresh=True, proxy_url=proxy_url)


def get_cli_schema_text() -> str:
    """Get CLI schema formatted as text for LLM prompts."""
    try:
        schema = get_cli_schema()
        return format_schema_for_llm(schema)
    except Exception as exc:
        # Fallback to hardcoded schema
        return """# Proxym CLI Schema (Fallback)

## Common Commands

- `proxym status` — Check server status
- `proxym ticket list` — List all tickets
- `proxym ticket add <description> -p <project> -t <tool>` — Create ticket
- `proxym project list` — List projects
- `proxym project add <path> --name <name>` — Register project
- `proxym board` — Show kanban board
- `proxym vm list` — List VMs
- `proxym tools list` — List available tools
- `proxym sprint list` — List sprints
- `proxym analyze <ticket_id>` — Analyze ticket
"""


if __name__ == "__main__":
    # Generate and save schema when run directly
    import sys

    from proxym.ctl import cli

    schema = generate_cli_schema(cli)

    if len(sys.argv) > 1:
        output_path = Path(sys.argv[1])
        save_schema_to_file(schema, output_path)
        print(f"✓ CLI schema saved to: {output_path}")
        print(f"  Commands: {len(schema['commands'])}")
        print(f"  Usage patterns: {len(schema['usage_patterns'])}")
    else:
        # Print formatted schema
        print(format_schema_for_llm(schema))
