"""
CLI entry point — the main orchestrator.

Usage:
    hld /path/to/codebase --provider anthropic --api-key sk-...
    hld ./my-project --provider none          # graph-only, no LLM
    python -m hld_generator /path/to/codebase
"""

from __future__ import annotations

import argparse
import logging
import sys
import time
import warnings
from pathlib import Path

from rich.console import Console
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
from rich.table import Table

from hld_generator import __version__
from hld_generator.config import HLDConfig
from hld_generator.scanner import FileScanner
from hld_generator.parsers.manager import ParserManager
from hld_generator.graph import GraphBuilder
from hld_generator.llm import LLMClient
from hld_generator.renderer import HLDRenderer

console = Console()


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="hld",
        description="Generate a High-Level Design document from any codebase.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""\
Examples:
  hld ./my-project
  hld ./my-project --provider anthropic --api-key sk-ant-...
  hld ./my-project --provider openai --api-key sk-...
  hld ./my-project --provider none --output ./docs/hld
  hld ./single_file.py
        """,
    )
    p.add_argument(
        "target",
        type=Path,
        help="Path to a source file or directory to analyse",
    )
    p.add_argument(
        "-o", "--output",
        type=Path,
        default=None,
        help="Output directory (default: ./hld_output)",
    )
    p.add_argument(
        "--provider",
        default="anthropic",
        help="LLM provider: anthropic, openai, none, or a plugin-registered name (default: anthropic)",
    )
    p.add_argument(
        "--model",
        default="",
        help="LLM model name (defaults: claude-sonnet-4-20250514 / gpt-4o)",
    )
    p.add_argument(
        "--api-key",
        default="",
        help="[Deprecated] API key — prefer ANTHROPIC_API_KEY / OPENAI_API_KEY env vars instead",
    )
    p.add_argument(
        "--include-tests",
        action="store_true",
        help="Include test files in the analysis",
    )
    p.add_argument(
        "--max-files",
        type=int,
        default=500,
        help="Maximum number of files to scan (default: 500)",
    )
    p.add_argument(
        "--max-file-size",
        type=int,
        default=512_000,
        help="Maximum file size in bytes (default: 512000)",
    )
    p.add_argument(
        "--format",
        default="",
        dest="output_format",
        help="Output format: built-in 'markdown' (default) or a plugin-registered renderer name",
    )
    p.add_argument(
        "--html",
        action="store_true",
        help="Also generate an interactive HTML viewer (hld_viewer.html) with pan/zoom diagrams",
    )
    p.add_argument(
        "--json",
        action="store_true",
        help="Also generate a structured JSON file (hld_data.json) for the React viewer",
    )
    p.add_argument(
        "--plugins-dir",
        type=Path,
        default=None,
        help="Directory containing plugin .py files to load",
    )
    p.add_argument(
        "--no-plugins",
        action="store_true",
        help="Disable all plugin loading (both --plugins-dir and auto-discovery)",
    )
    p.add_argument(
        "-v", "--verbose",
        action="store_true",
        help="Verbose logging",
    )
    p.add_argument(
        "--version",
        action="version",
        version=f"hld-generator {__version__}",
    )
    return p


def main(argv: list[str] | None = None) -> int:
    args = build_parser().parse_args(argv)

    # Logging
    level = logging.DEBUG if args.verbose else logging.INFO
    logging.basicConfig(
        level=level,
        format="%(levelname)s %(name)s: %(message)s",
    )

    try:
        return _run(args)
    except KeyboardInterrupt:
        console.print("\n[yellow]Interrupted.[/]")
        return 130
    except Exception as exc:
        console.print(f"\n[red]Error:[/] {exc}")
        logging.getLogger(__name__).debug("Full traceback:", exc_info=True)
        return 1


def _run(args: argparse.Namespace) -> int:
    """Inner pipeline — separated so main() can catch exceptions cleanly."""
    # Warn if --api-key is passed on CLI (visible in process list)
    if args.api_key:
        warnings.warn(
            "--api-key is deprecated (visible in process list). "
            "Use ANTHROPIC_API_KEY or OPENAI_API_KEY env vars instead.",
            DeprecationWarning,
            stacklevel=2,
        )

    # Load plugins
    from hld_generator.plugins import registry

    if not getattr(args, "no_plugins", False):
        if args.plugins_dir:
            n = registry.load_plugins_from_dir(args.plugins_dir)
            if n:
                console.print(f"  Loaded [cyan]{n}[/] plugin(s) from {args.plugins_dir}")

        # Also check for plugins/ dir next to the target (use parent for file targets)
        resolved_target = args.target.resolve()
        plugin_base = resolved_target if resolved_target.is_dir() else resolved_target.parent
        auto_plugin_dir = plugin_base / ".hld_plugins"
        if auto_plugin_dir.is_dir():
            console.print(
                f"  [yellow]⚠ Auto-loading plugins from {auto_plugin_dir}[/]\n"
                f"    [dim]These plugins execute arbitrary code. "
                f"Only proceed if you trust this codebase.[/]"
            )
            n = registry.load_plugins_from_dir(auto_plugin_dir)
            if n:
                console.print(f"  Loaded [cyan]{n}[/] plugin(s) from {auto_plugin_dir}")
    else:
        console.print("  [dim]Plugin loading disabled (--no-plugins)[/]")

    # Config
    config = HLDConfig(
        target=args.target.resolve(),
        llm_provider=args.provider,
        llm_model=args.model,
        api_key=args.api_key,
        output_dir=(args.output or Path("./hld_output")).resolve(),
        include_tests=args.include_tests,
        max_files=args.max_files,
        max_file_size=args.max_file_size,
    )

    # Run pre_scan hooks
    config = registry.run_hooks("pre_scan", config)

    console.print(
        Panel(
            f"[bold cyan]HLD Generator[/] v{__version__}\n"
            f"Target: [green]{config.target}[/]\n"
            f"Provider: [yellow]{config.llm_provider}[/]"
            + (f" ({config.llm_model})" if config.llm_model else ""),
            title="🏗️  High-Level Design Generator",
            expand=False,
        )
    )

    t0 = time.monotonic()

    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        TimeElapsedColumn(),
        console=console,
    ) as progress:

        # ── Step 1: Scan ───────────────────────────────────────────────
        task = progress.add_task("Scanning files...", total=None)
        scanner = FileScanner(config)
        files = scanner.scan()
        files = registry.run_hooks("post_scan", files)
        progress.update(task, description=f"Scanned [cyan]{len(files)}[/] files")
        progress.remove_task(task)

        if not files:
            console.print("[red]No parseable source files found.[/]")
            return 1

        # Show language breakdown
        lang_counts: dict[str, int] = {}
        for f in files:
            lang_counts[f.language] = lang_counts.get(f.language, 0) + 1
        table = Table(title="Detected Languages", show_lines=False)
        table.add_column("Language", style="cyan")
        table.add_column("Files", justify="right", style="green")
        for lang, count in sorted(lang_counts.items(), key=lambda x: -x[1]):
            table.add_row(lang, str(count))
        console.print(table)

        # ── Step 2: Parse ──────────────────────────────────────────────
        files = registry.run_hooks("pre_parse", files)
        task = progress.add_task("Parsing source files...", total=None)
        parser = ParserManager()
        parsed = parser.parse_all(files)
        progress.remove_task(task)
        parsed = registry.run_hooks("post_parse", parsed)

        total_classes = sum(len(p.classes) for p in parsed)
        total_funcs = sum(len(p.functions) for p in parsed)
        total_imports = sum(len(p.imports) for p in parsed)
        console.print(
            f"  Parsed: [cyan]{total_classes}[/] classes, "
            f"[cyan]{total_funcs}[/] functions, "
            f"[cyan]{total_imports}[/] imports"
        )

        # ── Step 3: Build graph ────────────────────────────────────────
        parsed = registry.run_hooks("pre_graph", parsed)
        task = progress.add_task("Building dependency graph...", total=None)
        graph_builder = GraphBuilder()
        code_graph = graph_builder.build(parsed, target_dir=config.target if config.target.is_dir() else config.target.parent)
        code_graph = registry.run_hooks("post_graph", code_graph)
        progress.update(
            task,
            description=f"Graph: [cyan]{code_graph.graph.number_of_nodes()}[/] nodes, "
                        f"[cyan]{code_graph.graph.number_of_edges()}[/] edges",
        )
        progress.remove_task(task)

        # ── Step 4: LLM analysis ───────────────────────────────────────
        task = progress.add_task(
            f"Running {'LLM' if config.llm_provider != 'none' else 'static'} analysis...",
            total=None,
        )
        code_graph = registry.run_hooks("pre_llm", code_graph)
        llm = LLMClient(config)
        analysis = llm.analyse(code_graph)
        analysis = registry.run_hooks("post_llm", analysis)
        desc = (
            f"Analysis complete: [green]{analysis.project_name}[/]"
            if not analysis.error
            else "Analysis complete [yellow](with warnings)[/]"
        )
        progress.update(task, description=desc)
        progress.remove_task(task)

        # ── Step 5: Render output ──────────────────────────────────────
        task = progress.add_task("Generating HLD documents...", total=None)
        result = registry.run_hooks("pre_render", analysis, code_graph)
        if isinstance(result, tuple) and len(result) == 2:
            analysis, code_graph = result

        # Select renderer: explicit --format, plugin fallback, or built-in
        fmt = args.output_format
        plugin_renderers = registry.available_renderers
        if fmt and fmt != "markdown":
            if fmt in plugin_renderers:
                output_files = plugin_renderers[fmt].render(analysis, code_graph, config.output_dir)
            else:
                available = ", ".join(plugin_renderers.keys()) if plugin_renderers else "none"
                console.print(
                    f"[red]Unknown format '{fmt}'. "
                    f"Available plugin formats: {available}. "
                    f"Falling back to built-in markdown.[/]"
                )
                renderer = HLDRenderer(config.output_dir)
                output_files = renderer.render(analysis, code_graph)
        else:
            renderer = HLDRenderer(config.output_dir)
            output_files = renderer.render(analysis, code_graph)
        output_files = registry.run_hooks("post_render", output_files)

        # Generate interactive HTML viewer if requested
        if getattr(args, "html", False):
            from hld_generator.html_renderer import HTMLRenderer
            html_renderer = HTMLRenderer(config.output_dir)
            output_files.extend(html_renderer.render(analysis, code_graph))

        # Generate structured JSON for React viewer if requested
        if getattr(args, "json", False):
            from hld_generator.json_renderer import JSONRenderer
            json_renderer = JSONRenderer(config.output_dir)
            output_files.extend(json_renderer.render(analysis, code_graph))

        progress.remove_task(task)

    elapsed = time.monotonic() - t0

    # Summary
    console.print()
    console.print(
        Panel(
            "\n".join(
                f"  📄 [link=file://{f}]{f}[/link]" for f in output_files
            )
            + f"\n\n  ⏱  Completed in {elapsed:.1f}s",
            title="✅ Output Files",
            style="green",
            expand=False,
        )
    )

    # Exit code: 0 = success, 2 = degraded (LLM failed but fallback used)
    if analysis.error:
        return 2
    return 0


if __name__ == "__main__":
    sys.exit(main())
