"""CLI for docs context operations."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Annotated, Literal, cast

import typer
import uvicorn

from common.server import create_app
from docsctx.api.routes import build_router
from docsctx.config import DocsSettings
from docsctx.db.repository import DocsRepository
from docsctx.embedder import create_embedder, validate_embedder_access
from docsctx.finalize import run_finalize
from docsctx.indexer import DocsIndexer, SUPPORTED_EXTENSIONS
from docsctx.loader import load_path
from docsctx.provider import create_provider, validate_provider_access

app = typer.Typer(help="Docs context commands.", no_args_is_help=True)


def _validate_llm_runtime(settings: DocsSettings) -> None:
    typer.echo("🔐 Validating LLM provider configuration...")
    provider = validate_provider_access(settings, create_provider(settings))
    typer.echo(f"✅ LLM provider '{settings.chat.provider}' is reachable.")

    typer.echo("🧠 Validating embedding backend configuration...")
    validate_embedder_access(settings, create_embedder(settings))
    typer.echo("✅ Embedding backend is reachable.")


def build_app(settings: DocsSettings | None = None):
    """Create the docsctx ASGI app."""
    resolved_settings = settings or DocsSettings()
    repository = DocsRepository(resolved_settings.db_path)
    repository.create_schema()

    app_instance = create_app("docsctx", "0.1.0")
    app_instance.include_router(build_router(repository, resolved_settings))
    return app_instance


@app.command("index")
def index(
    path: Annotated[str | None, typer.Argument(help="Directory or file to index.")] = None,
    url: str | None = typer.Option(None, "--url", help="Root URL to crawl and index."),
    db_path: str | None = typer.Option(None, "--db", help="Path to the docsctx SQLite database."),
    chunk_size: int | None = typer.Option(None, "--chunk-size", help="Chunk size in characters."),
    chunk_overlap: int | None = typer.Option(None, "--chunk-overlap", help="Chunk overlap in characters."),
    max_pages: int | None = typer.Option(None, "--max-pages", help="Maximum crawled pages for --url."),
    max_depth: int | None = typer.Option(None, "--max-depth", help="Maximum crawl depth for --url."),
    allow_external: bool = typer.Option(False, "--allow-external", help="Allow links outside the root host."),
    same_path_prefix: bool = typer.Option(True, "--same-path-prefix/--no-same-path-prefix", help="Restrict crawling to the root path prefix."),
    timeout: float = typer.Option(10.0, "--timeout", help="HTTP timeout for website crawling."),
    finalize: bool = typer.Option(False, "--finalize", help="Run structured and community analysis after indexing."),
    config: str | None = typer.Option(None, "--config", help="Path to a YAML or properties configuration file."),
) -> None:
    """Index local documents or crawl a documentation URL."""
    if path is None and url is None and not finalize:
        typer.echo("❌ Path, --url, or --finalize is required")
        raise typer.Exit(code=1)

    settings = DocsSettings(config_file=config)
    if finalize:
        try:
            _validate_llm_runtime(settings)
        except Exception as exc:
            typer.echo(f"❌ LLM validation failed: {type(exc).__name__}: {exc}")
            raise typer.Exit(code=2)
    repository = DocsRepository(db_path or settings.db_path)
    indexer = DocsIndexer(
        repository,
        chunk_size=chunk_size or settings.chunk_size,
        chunk_overlap=chunk_overlap or settings.chunk_overlap,
    )
    
    typer.echo("🚀 Starting Docs Context workflow...")

    if url is not None:
        typer.echo(f"🌐 Crawling and indexing url: {url}")
        summary = indexer.index_url(
            url,
            max_pages=max_pages if max_pages is not None else settings.max_pages,
            max_depth=max_depth if max_depth is not None else settings.max_depth,
            allow_external=allow_external,
            same_path_prefix=same_path_prefix,
            timeout=timeout,
        )
        typer.echo(f"  📄 Indexed documents: {summary.indexed_documents}")
        typer.echo(f"  ⏭️  Skipped documents: {summary.skipped_documents}")
    elif path is not None:
        path_obj = Path(path)
        if path_obj.is_file():
            if path_obj.suffix.lower() not in SUPPORTED_EXTENSIONS:
                typer.echo(
                    f"❌ Skipping {path_obj.name}: unsupported extension '{path_obj.suffix}'."
                )
                typer.echo(f"Supported extensions: {', '.join(sorted(SUPPORTED_EXTENSIONS))}")
                raise typer.Exit(code=2)
            loaded = load_path(path_obj)
            if loaded is None or not loaded.content:
                typer.echo(f"❌ Skipping {path_obj.name}: failed to parse or empty content.")
                raise typer.Exit(code=2)
        typer.echo(f"📂 Indexing local path: {path}")
        summary = indexer.index_path(path)
        typer.echo(f"  📄 Indexed documents: {summary.indexed_documents}")
        typer.echo(f"  ⏭️  Skipped documents: {summary.skipped_documents}")

    if finalize:
        typer.echo("🧠 Running 'finalize' graph structuring & community analysis...")
        embedder = create_embedder(settings)
        finalize_summary = run_finalize(repository, settings, embedder=embedder)
        typer.echo(f"  ✅ Finalized documents: {finalize_summary['documents']}")
        typer.echo(f"  🌍 Communities populated: {finalize_summary['communities']}")

    typer.echo("\n✅ Complete! Current Database Statistics:")
    stats_snapshot = repository.get_stats()
    typer.echo(f"  📄 Total Documents: {stats_snapshot.documents}")
    typer.echo(f"  🧩 Total Chunks: {stats_snapshot.chunks}")
    typer.echo(f"  🧱 Total Entities: {stats_snapshot.entities}")
    typer.echo(f"  🔗 Total Relationships: {stats_snapshot.relationships}")
    typer.echo(f"  🌍 Total Communities: {stats_snapshot.communities}")


@app.command("stats")
def stats(
    db_path: str | None = typer.Option(None, "--db", help="Path to the docsctx SQLite database."),
    as_json: bool = typer.Option(False, "--json", help="Output stats as JSON."),
    config: str | None = typer.Option(None, "--config", help="Path to a YAML or properties configuration file."),
) -> None:
    """Show docs index statistics."""
    settings = DocsSettings(config_file=config)
    repository = DocsRepository(db_path or settings.db_path)
    repository.create_schema()
    snapshot = repository.get_stats().to_dict()
    if as_json:
        typer.echo(json.dumps(snapshot))
        return
    typer.echo("📈 Document Context Database Statistics:")
    for key, value in snapshot.items():
        typer.echo(f"  {key.capitalize()}: {value}")


@app.command("clean")
def clean(
    db_path: str | None = typer.Option(None, "--db", help="Path to the docsctx SQLite database."),
    force: bool = typer.Option(False, "--force", "-f", help="Force clear without prompting."),
    config: str | None = typer.Option(None, "--config", help="Path to a YAML or properties configuration file."),
) -> None:
    """Clear the docs index database."""
    if not force:
        typer.confirm("⚠️  This will delete all docsctx data. Continue?", abort=True)
    settings = DocsSettings(config_file=config)
    repository = DocsRepository(db_path or settings.db_path)
    repository.clear()
    typer.echo("🗑️  Docsctx database cleared.")


@app.command("serve")
def serve(
    host: str | None = typer.Option(None, "--host", help="Bind host."),
    port: int | None = typer.Option(None, "--port", help="Bind port."),
    db_path: str | None = typer.Option(None, "--db", help="Path to the docsctx SQLite database."),
    config: str | None = typer.Option(None, "--config", help="Path to a YAML or properties configuration file."),
) -> None:
    """Run the docsctx API server."""
    settings = DocsSettings(config_file=config)
    if db_path is not None:
        settings = settings.model_copy(update={"db_path": Path(db_path)})
    try:
        _validate_llm_runtime(settings)
    except Exception as exc:
        typer.echo(f"❌ LLM validation failed: {type(exc).__name__}: {exc}")
        raise typer.Exit(code=2)
    uvicorn.run(build_app(settings), host=host or settings.host, port=port or settings.port)


@app.command("mcp")
def mcp_command(
    transport: Literal["stdio", "sse", "streamable-http"] = typer.Option("stdio", "--transport", help="Transport type: stdio, sse, or streamable-http."),
    db_path: str | None = typer.Option(None, "--db", help="Path to the docsctx SQLite database."),
    config: str | None = typer.Option(None, "--config", help="Path to a YAML or properties configuration file."),
) -> None:
    """Run the docsctx MCP server."""
    from docsctx.mcp import build_mcp_server

    settings = DocsSettings(config_file=config)
    if db_path is not None:
        settings = settings.model_copy(update={"db_path": Path(db_path)})
    try:
        _validate_llm_runtime(settings)
    except Exception as exc:
        typer.echo(f"❌ LLM validation failed: {type(exc).__name__}: {exc}")
        raise typer.Exit(code=2)
    repository = DocsRepository(settings.db_path)
    repository.create_schema()
    server = build_mcp_server(repository, settings)
    server.run(transport=cast(Literal["stdio", "sse", "streamable-http"], transport))


@app.command("setup")
def setup_mcp_config(
    output: str | None = typer.Option(None, "--output", "-o", help="Output path for mcp.json (default: ~/.mcp.json)."),
    transport: str = typer.Option("stdio", "--transport", help="Transport: stdio, sse, streamable-http."),
) -> None:
    """Write or update the VS Code mcp.json config for docsctx."""
    from docsctx.mcp import build_mcp_server_entry

    config_path = Path(output or "~/.mcp.json").expanduser()
    
    if config_path.exists():
        with open(config_path) as f:
            mcp_config = json.load(f)
    else:
        mcp_config = {"servers": {}, "inputs": []}

    entry = build_mcp_server_entry(transport=transport)
    mcp_config["servers"]["docsctx"] = entry
    
    with open(config_path, "w") as f:
        json.dump(mcp_config, f, indent=2)
    
    typer.echo(f"MCP config written to {config_path}")


def compat_main() -> None:
    """Compatibility entrypoint for legacy `docscontext` command."""
    app()

