"""
Docs command group for PraisonAI CLI.

Provides documentation commands including code execution validation.

Usage:
    praisonai docs run                    # Run all Python blocks from docs
    praisonai docs list                   # List discovered code blocks
    praisonai docs run --dry-run          # Extract only, no execution
"""

from datetime import datetime
from pathlib import Path
from typing import Optional, List

import typer

app = typer.Typer(help="Documentation management and code validation")


def _get_default_docs_path() -> Path:
    """Get default docs path."""
    candidates = [
        Path("/Users/praison/PraisonAIDocs"),
        Path.cwd() / "docs",
        Path(__file__).parent.parent.parent.parent.parent / "docs",
    ]
    
    for candidate in candidates:
        if candidate.exists() and candidate.is_dir():
            return candidate
    
    return Path.cwd()


def _get_default_report_dir() -> Path:
    """Get default report directory with timestamp."""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    return Path.cwd() / "reports" / "docs" / timestamp


@app.command("run")
def docs_run(
    docs_path: Optional[Path] = typer.Option(
        None,
        "--docs-path", "-p",
        help="Path to documentation directory",
    ),
    include: Optional[List[str]] = typer.Option(
        None,
        "--include", "-i",
        help="Include patterns (glob), can be specified multiple times",
    ),
    exclude: Optional[List[str]] = typer.Option(
        None,
        "--exclude", "-e",
        help="Exclude patterns (glob), can be specified multiple times",
    ),
    group: Optional[List[str]] = typer.Option(
        None,
        "--group", "-g",
        help="Run only specific groups (top-level dirs), can be repeated",
    ),
    languages: str = typer.Option(
        "python",
        "--languages", "-l",
        help="Languages to execute (comma-separated)",
    ),
    timeout: int = typer.Option(
        60,
        "--timeout", "-t",
        help="Per-snippet timeout in seconds",
    ),
    max_snippets: Optional[int] = typer.Option(
        None,
        "--max-snippets",
        help="Maximum snippets to process",
    ),
    fail_fast: bool = typer.Option(
        False,
        "--fail-fast", "-x",
        help="Stop on first failure",
    ),
    mode: str = typer.Option(
        "lenient",
        "--mode", "-m",
        help="Mode: 'lenient' or 'strict' (strict fails on not_run)",
    ),
    no_stream: bool = typer.Option(
        False,
        "--no-stream",
        help="Don't stream output to terminal",
    ),
    report_dir: Optional[Path] = typer.Option(
        None,
        "--report-dir", "-r",
        help="Directory for reports (default: ./reports/docs/<timestamp>)",
    ),
    no_json: bool = typer.Option(
        False,
        "--no-json",
        help="Skip JSON report generation",
    ),
    no_md: bool = typer.Option(
        False,
        "--no-md",
        help="Skip Markdown report generation",
    ),
    no_csv: bool = typer.Option(
        False,
        "--no-csv",
        help="Skip CSV report generation",
    ),
    require_env: Optional[List[str]] = typer.Option(
        None,
        "--require-env",
        help="Required env vars (skip all if missing)",
    ),
    python: Optional[str] = typer.Option(
        None,
        "--python",
        help="Python executable to use (default: current interpreter)",
    ),
    quiet: bool = typer.Option(
        False,
        "--quiet", "-q",
        help="Minimal output",
    ),
):
    """
    Run Python code blocks from documentation files.
    
    Extracts code blocks from Mintlify docs, classifies them as runnable,
    executes them, and generates reports (JSON, Markdown, CSV).
    
    Examples:
        praisonai docs run
        praisonai docs run --docs-path /path/to/docs --timeout 120
        praisonai docs run --group models --group tools --max-snippets 10
        praisonai docs run --include "quickstart*" --fail-fast
    """
    # Lazy import to avoid loading at CLI startup
    from praisonai.suite_runner import DocsSource, SuiteExecutor, RunResult
    
    # Resolve paths
    docs = docs_path or _get_default_docs_path()
    output_dir = report_dir or _get_default_report_dir()
    
    if not docs.exists():
        typer.echo(f"❌ Docs path not found: {docs}")
        raise typer.Exit(2)
    
    # Parse languages
    lang_list = [lang.strip() for lang in languages.split(',') if lang.strip()]
    
    # Create source
    source = DocsSource(
        root=docs,
        languages=lang_list,
        include_patterns=list(include) if include else None,
        exclude_patterns=list(exclude) if exclude else None,
        groups=list(group) if group else None,
    )
    
    # Discover items
    items = source.discover()
    
    # Create executor
    executor = SuiteExecutor(
        suite="docs",
        source_path=docs,
        timeout=timeout,
        fail_fast=fail_fast,
        stream_output=not no_stream,
        max_items=max_snippets,
        require_env=list(require_env) if require_env else None,
        report_dir=output_dir,
        generate_json=not no_json,
        generate_md=not no_md,
        generate_csv=not no_csv,
        python_executable=python,
        pythonpath_additions=source.get_pythonpath(),
        groups=list(group) if group else None,
    )
    
    # Status icons
    icons = {
        "passed": "✅",
        "failed": "❌",
        "skipped": "⏭️",
        "timeout": "⏱️",
        "not_run": "📝",
        "xfail": "⚠️",
    }
    
    def on_item_start(item, idx: int, total: int):
        if not quiet:
            typer.echo(f"\n[{idx}/{total}] {item.display_name}")
    
    def on_item_end(result: RunResult, idx: int, total: int):
        icon = icons.get(result.status, "?")
        duration = f"{result.duration_seconds:.2f}s" if result.duration_seconds else ""
        
        if quiet:
            typer.echo(f"{icon} {result.display_name}")
        else:
            msg = f"  {icon} {result.status.upper()}"
            if duration:
                msg += f" ({duration})"
            if result.skip_reason:
                msg += f" - {result.skip_reason[:50]}"
            if result.error_message and not no_stream:
                msg += f"\n     Error: {result.error_message[:100]}"
            typer.echo(msg)
    
    def on_output(line: str, stream: str):
        if not quiet and not no_stream:
            prefix = "  │ " if stream == "stdout" else "  │ [err] "
            typer.echo(f"{prefix}{line.rstrip()}")
    
    # Print header
    if not quiet:
        typer.echo("=" * 60)
        typer.echo("PraisonAI Docs Code Execution")
        typer.echo("=" * 60)
        typer.echo(f"Docs Path: {docs}")
        typer.echo(f"Timeout: {timeout}s")
        typer.echo(f"Mode: {mode}")
        if group:
            typer.echo(f"Groups: {', '.join(group)}")
        typer.echo(f"Reports: {output_dir}")
        typer.echo(f"Items: {len(items)}")
    
    # Run
    report = executor.run(
        items=items,
        on_item_start=on_item_start,
        on_item_end=on_item_end,
        on_output=on_output if not no_stream else None,
    )
    
    # Update report with CLI args
    report.cli_args = [f"--docs-path={docs}", f"--timeout={timeout}"]
    if group:
        for g in group:
            report.cli_args.append(f"--group={g}")
    
    # Print summary
    totals = report.totals
    total_count = sum(totals.values())
    
    if not quiet:
        typer.echo("\n" + "=" * 60)
        typer.echo("SUMMARY")
        typer.echo("=" * 60)
        typer.echo(f"  ✅ Passed:  {totals['passed']}")
        typer.echo(f"  ❌ Failed:  {totals['failed']}")
        typer.echo(f"  ⏭️ Skipped: {totals['skipped']}")
        typer.echo(f"  ⏱️ Timeout: {totals['timeout']}")
        typer.echo(f"  📝 Not Run: {totals['not_run']}")
        typer.echo("  ─────────────────")
        typer.echo(f"  Total:     {total_count}")
        typer.echo("=" * 60)
        
        if output_dir.exists():
            typer.echo(f"\n📁 Reports saved to: {output_dir}")
    
    # Exit code
    if totals['failed'] > 0 or totals['timeout'] > 0:
        raise typer.Exit(1)
    
    if mode == "strict" and totals['not_run'] > 0:
        raise typer.Exit(1)
    
    raise typer.Exit(0)


@app.command("list")
def docs_list(
    docs_path: Optional[Path] = typer.Option(
        None,
        "--docs-path", "-p",
        help="Path to documentation directory",
    ),
    include: Optional[List[str]] = typer.Option(
        None,
        "--include", "-i",
        help="Include patterns (glob)",
    ),
    exclude: Optional[List[str]] = typer.Option(
        None,
        "--exclude", "-e",
        help="Exclude patterns (glob)",
    ),
    group: Optional[List[str]] = typer.Option(
        None,
        "--group", "-g",
        help="Filter by group (top-level dir)",
    ),
    languages: str = typer.Option(
        "python",
        "--languages", "-l",
        help="Languages to show (comma-separated)",
    ),
    show_code: bool = typer.Option(
        False,
        "--code", "-c",
        help="Show code preview",
    ),
    show_groups: bool = typer.Option(
        False,
        "--groups",
        help="Show available groups only",
    ),
):
    """
    List discovered code blocks from documentation.
    
    Examples:
        praisonai docs list
        praisonai docs list --groups
        praisonai docs list --group models --code
    """
    from praisonai.suite_runner import DocsSource
    
    docs = docs_path or _get_default_docs_path()
    
    if not docs.exists():
        typer.echo(f"❌ Docs path not found: {docs}")
        raise typer.Exit(2)
    
    # Parse languages
    lang_list = [lang.strip() for lang in languages.split(',') if lang.strip()]
    
    source = DocsSource(
        root=docs,
        languages=lang_list,
        include_patterns=list(include) if include else None,
        exclude_patterns=list(exclude) if exclude else None,
        groups=list(group) if group else None,
    )
    
    # Show groups only
    if show_groups:
        groups = source.get_groups()
        typer.echo(f"Available groups in {docs}:\n")
        for g in groups:
            typer.echo(f"  - {g}")
        return
    
    items = source.discover()
    
    typer.echo(f"Found {len(items)} code blocks in {docs}\n")
    
    for idx, item in enumerate(items, 1):
        rel_path = item.source_path.relative_to(docs) if docs in item.source_path.parents else item.source_path
        
        status = "✅ Runnable" if item.runnable else "📝 Partial"
        
        typer.echo(f"{idx:3}. [{item.group}] {rel_path}:{item.line_start}-{item.line_end}")
        typer.echo(f"     {status} ({item.runnable_decision})")
        
        if show_code:
            preview = item.code[:100].replace('\n', ' ')
            if len(item.code) > 100:
                preview += "..."
            typer.echo(f"     Code: {preview}")
        
        typer.echo()


@app.command("generate")
def docs_generate(
    source: str = typer.Argument(".", help="Source directory"),
    output: str = typer.Option("docs", "--output", "-o", help="Output directory"),
):
    """Generate documentation."""
    from praisonai.cli.main import PraisonAI
    import sys
    
    argv = ['docs', 'generate', source, '--output', output]
    
    original_argv = sys.argv
    sys.argv = ['praisonai'] + argv
    
    try:
        praison = PraisonAI()
        praison.main()
    except SystemExit:
        pass
    finally:
        sys.argv = original_argv


@app.command("serve")
def docs_serve(
    port: int = typer.Option(8000, "--port", "-p", help="Port to serve on"),
):
    """Serve documentation locally."""
    from praisonai.cli.main import PraisonAI
    import sys
    
    argv = ['docs', 'serve', '--port', str(port)]
    
    original_argv = sys.argv
    sys.argv = ['praisonai'] + argv
    
    try:
        praison = PraisonAI()
        praison.main()
    except SystemExit:
        pass
    finally:
        sys.argv = original_argv
