"""CLI entry point — analyze, serve, status commands."""

from __future__ import annotations

import os
import sys
from pathlib import Path

import click
from rich.console import Console

_is_tty = hasattr(sys.stderr, "isatty") and sys.stderr.isatty()
console = Console(stderr=True, force_terminal=_is_tty, highlight=False)


@click.group()
def main():
    """CodeGraph — Local Code Intelligence Engine with MCP Gateway."""
    pass


def _index_project(abs_path: str):
    """Shared indexing logic: run pipeline, store in Neo4j, build search index.

    Returns (index, graph, search) tuple. graph may be None if Neo4j unavailable.
    """
    from .core.ingestion.pipeline import run_pipeline
    from .core.graph.store import GraphStore
    from .core.search.bm25_index import SymbolSearchIndex
    from .core.models import NodeType

    index = run_pipeline(abs_path)

    # Store in Neo4j
    console.print("\n[bold blue]Storing in Neo4j...[/bold blue]")
    graph = GraphStore()
    try:
        graph.connect()
        graph.store_index(index)
    except Exception as e:
        console.print(f"[yellow]⚠ Neo4j unavailable:[/yellow] {e}")
        console.print("  Graph features disabled. Run: docker compose up -d neo4j")
        graph = None

    # Build BM25 search index
    _code_types = frozenset((NodeType.FUNCTION, NodeType.CLASS, NodeType.METHOD))
    search = SymbolSearchIndex()
    symbols = [
        {
            "uid": n.uid,
            "name": n.name,
            "node_type": n.node_type.value,
            "file_path": n.file_path,
            "line_start": n.line_start,
            "line_end": n.line_end,
            "signature": n.signature,
            "docstring": n.docstring,
            "language": n.language,
        }
        for n in index.nodes
        if n.node_type in _code_types
    ]
    search.build(symbols)
    console.print(f"[green]✓[/green] Search index: {len(symbols)} symbols indexed")

    return index, graph, search


@main.command()
@click.argument("path", default=".", type=click.Path(exists=True))
@click.option("--force", is_flag=True, help="Force full re-index")
def analyze(path: str, force: bool):
    """Index a project directory. Parses code, builds knowledge graph."""
    from .core.ingestion.pipeline import PipelineInterrupted

    abs_path = str(Path(path).resolve())
    try:
        _index_project(abs_path)
    except PipelineInterrupted:
        sys.exit(1)
    console.print(f"\n[bold green]Ready![/bold green] Run [cyan]codegraph serve {path}[/cyan] to start MCP server.\n")


@main.command()
@click.argument("path", default="", type=str)
@click.option("--host", default="0.0.0.0", help="Server host")
@click.option("--port", default=8000, type=int, help="Server port")
@click.option("--mcp-only", is_flag=True, help="Start MCP stdio server only (for Claude)")
@click.option("--open", "open_browser", is_flag=True, help="Auto-open dashboard in browser")
def serve(path: str, host: str, port: int, mcp_only: bool, open_browser: bool):
    """Start the MCP server + REST API.

    Can start with or without a project path.
    Without path: starts in standby mode — use index_project tool from Claude.
    With path: pre-indexes the project, then starts server.
    """
    from .mcp.server import mcp as mcp_server, init_mcp

    graph = None
    search = None
    hybrid = None

    if path and Path(path).is_dir():
        abs_path = str(Path(path).resolve())
        _index, graph, search = _index_project(abs_path)

        hybrid = _try_init_hybrid(search)
        init_mcp(graph, search, abs_path, hybrid=hybrid)
    else:
        init_mcp()
        if not mcp_only:
            console.print("[bold yellow]Standby mode[/bold yellow] — no project loaded")
            console.print("  Use 'index_project' tool from Claude to index a project.\n")

    if mcp_only:
        mcp_server.run(transport="stdio")
    else:
        import uvicorn
        from .api.routes import app, init_api

        init_api(graph=graph, search=search, hybrid=hybrid)

        console.print(f"\n[bold green]Servers starting:[/bold green]")
        console.print(f"  REST API:  http://{host}:{port}/api")
        console.print(f"  MCP:       stdio (use --mcp-only for Claude Desktop)\n")

        if open_browser:
            import webbrowser
            import threading
            url = f"http://localhost:{port}/api"
            threading.Timer(1.5, webbrowser.open, args=[url]).start()
            console.print(f"  [cyan]Opening browser → {url}[/cyan]\n")

        uvicorn.run(app, host=host, port=port)


@main.command()
def status():
    """Show current index status."""
    from .core.graph.store import GraphStore

    graph = GraphStore()
    try:
        graph.connect()
        stats = graph.get_stats()
        console.print(f"[green]✓[/green] Neo4j connected")
        console.print(f"  Nodes: {stats['nodes']}")
        console.print(f"  Edges: {stats['edges']}")
    except Exception as e:
        console.print(f"[red]✗[/red] Neo4j: {e}")

    try:
        from .core.search.vector_store import VectorStore
        vs = VectorStore()
        count = vs.count()
        console.print(f"[green]✓[/green] Qdrant ({vs.mode} mode) — {count} vectors")
    except ImportError:
        console.print(f"[dim]  Qdrant: not installed (optional)[/dim]")
    except Exception as e:
        console.print(f"[dim]  Qdrant: not available ({e})[/dim]")


@main.command()
def update():
    """Update codegraph to the latest version from npm."""
    import subprocess
    import json as _json

    # Get current version
    pkg_json = Path(__file__).resolve().parent.parent.parent / "package.json"
    try:
        current = _json.loads(pkg_json.read_text()).get("version", "unknown")
    except Exception:
        current = "unknown"

    console.print(f"  Current version: [bold]{current}[/bold]")
    console.print("  [dim]Checking npm registry...[/dim]")

    # Check latest on npm
    try:
        result = subprocess.run(
            ["npm", "view", "codegraph-smart-mcp", "version"],
            capture_output=True, text=True, timeout=15, shell=True,
        )
        latest = result.stdout.strip()
        if not latest:
            console.print("[red]✗[/red] Could not reach npm registry")
            return
    except Exception as e:
        console.print(f"[red]✗[/red] Could not check npm: {e}")
        return

    if latest == current:
        console.print(f"[green]✓[/green] Already on latest version ({current})")
        return

    console.print(f"  [bold blue]Updating {current} → {latest}...[/bold blue]\n")
    try:
        subprocess.run(
            ["npm", "install", "-g", "codegraph-smart-mcp@latest"],
            timeout=300, check=True, shell=True,
        )
        console.print(f"\n[green]✓[/green] Updated to {latest}")
    except subprocess.CalledProcessError:
        console.print("[red]✗[/red] Update failed. Try manually: npm install -g codegraph-smart-mcp@latest")
    except Exception as e:
        console.print(f"[red]✗[/red] {e}")


def _try_init_hybrid(bm25_search):
    """Try to initialize hybrid search. Returns None if unavailable."""
    from .core.search.vector_store import VectorStore
    from .core.search.embedder import Embedder
    from .core.search.hybrid_search import HybridSearch

    try:
        vs = VectorStore()
        count = vs.count()
        if count == 0:
            console.print("[dim]  Hybrid search: no vectors (run analyze first)[/dim]")
            return None

        embedder = Embedder()
        hybrid = HybridSearch(bm25_search, vs, embedder)
        console.print(f"[green]✓[/green] Hybrid search enabled ({count} vectors)")
        return hybrid
    except Exception as e:
        console.print(f"[dim]  Hybrid search: unavailable ({e})[/dim]")
        return None


if __name__ == "__main__":
    main()
