"""
RBSP Command-Line Interface.

Usage::

    rbsp init                  Initialize RBSP in current directory
    rbsp index [path]          Index a directory
    rbsp search "query"        Search indexed content
    rbsp status                Show index status and statistics
    rbsp watch [path]          Start file watcher for live indexing
    rbsp config [key] [value]  Manage configuration
"""

from __future__ import annotations

import argparse
import json
import signal
import sys
import time
from pathlib import Path
from typing import List, Optional, Sequence

from rbsp.runtime.platform import ANSI


# ---------------------------------------------------------------------------
# Output helpers
# ---------------------------------------------------------------------------

def _print_header(text: str) -> None:
    """Print a bold header."""
    print(f"\n{ANSI.bold}{ANSI.cyan}{text}{ANSI.reset}")
    print(f"{ANSI.dim}{'─' * min(len(text) + 4, 60)}{ANSI.reset}")


def _print_result(index: int, path: str, score: float, line: int, snippet: str) -> None:
    """Pretty-print a single search result."""
    score_color = ANSI.green if score >= 0.7 else ANSI.yellow if score >= 0.4 else ANSI.dim
    print(
        f"  {ANSI.bold}{index}.{ANSI.reset} "
        f"{ANSI.blue}{path}{ANSI.reset}"
        f"{ANSI.dim}:{line}{ANSI.reset}"
        f"  {score_color}({score:.2f}){ANSI.reset}"
    )
    if snippet:
        for sline in snippet.splitlines()[:3]:
            print(f"     {ANSI.dim}{sline.rstrip()}{ANSI.reset}")
    print()


def _print_error(msg: str) -> None:
    """Print an error message."""
    print(f"{ANSI.red}Error:{ANSI.reset} {msg}", file=sys.stderr)


def _print_success(msg: str) -> None:
    """Print a success message."""
    print(f"{ANSI.green}OK:{ANSI.reset} {msg}")


def _print_info(msg: str) -> None:
    """Print an informational message."""
    print(f"{ANSI.cyan}Info:{ANSI.reset} {msg}")


# ---------------------------------------------------------------------------
# Subcommands
# ---------------------------------------------------------------------------

def _cmd_init(args: argparse.Namespace) -> int:
    """Handle ``rbsp init``."""
    from rbsp.runtime.engine import RBSPEngine

    path = args.path or "."
    engine = RBSPEngine.get_instance()
    engine.init(path)

    _print_success(f"Initialized RBSP in {Path(path).resolve()}")
    _print_info("Created .rbsp/ directory with default configuration")
    _print_info("Run 'rbsp index' to build the search index")
    return 0


def _cmd_index(args: argparse.Namespace) -> int:
    """Handle ``rbsp index``."""
    from rbsp.runtime.engine import RBSPEngine

    path = args.path or "."
    engine = RBSPEngine.get_instance()
    if not engine.is_initialized:
        engine.init(path)

    _print_info(f"Indexing {Path(path).resolve()} ...")
    stats = engine.index(path, force=args.force)

    _print_header("Index Complete")
    print(f"  Files indexed:  {ANSI.bold}{stats.files_indexed}{ANSI.reset}")
    print(f"  Files skipped:  {stats.files_skipped}")
    print(f"  Total size:     {_format_size(stats.total_size)}")
    print(f"  Duration:       {stats.duration:.3f}s")
    print(f"  Index path:     {ANSI.dim}{stats.index_path}{ANSI.reset}")
    print()
    return 0


def _cmd_search(args: argparse.Namespace) -> int:
    """Handle ``rbsp search``."""
    from rbsp.runtime.engine import RBSPEngine

    query = " ".join(args.query)
    if not query.strip():
        _print_error("Please provide a search query")
        return 1

    engine = RBSPEngine.get_instance()
    scope = args.scope or "."
    if not engine.is_initialized:
        engine.init(scope)

    start = time.monotonic()
    results = engine.resolve(query, scope=scope, max_results=args.limit)
    elapsed = time.monotonic() - start

    if not results:
        _print_info(f"No results for: {query}")
        return 0

    _print_header(f"Results for: {query}")
    print(f"  {ANSI.dim}Found {len(results)} results in {elapsed:.3f}s{ANSI.reset}\n")

    for i, r in enumerate(results, 1):
        _print_result(i, r.path, r.score, r.line, r.snippet if args.verbose else "")

    if args.json:
        data = [
            {"path": r.path, "score": r.score, "line": r.line, "snippet": r.snippet}
            for r in results
        ]
        print(json.dumps(data, indent=2))

    return 0


def _cmd_status(args: argparse.Namespace) -> int:
    """Handle ``rbsp status``."""
    from rbsp.runtime.engine import RBSPEngine

    engine = RBSPEngine.get_instance()
    path = args.path or "."
    if not engine.is_initialized:
        engine.init(path)

    info = engine.status()

    _print_header("RBSP Status")
    print(f"  Platform:       {ANSI.bold}{info['platform']}{ANSI.reset}")
    print(f"  Initialized:    {info['initialized']}")
    print(f"  Index entries:  {ANSI.bold}{info['index_entries']}{ANSI.reset}")
    print(f"  Watching:       {info['watching']}")
    print(f"  Config root:    {ANSI.dim}{info['config_root']}{ANSI.reset}")

    stats = info.get("stats", {})
    if stats:
        print(f"\n  {ANSI.bold}Statistics:{ANSI.reset}")
        print(f"    Queries:          {stats.get('queries', 0)}")
        print(f"    Index operations: {stats.get('index_operations', 0)}")
        print(f"    Files indexed:    {stats.get('files_indexed', 0)}")

    plugins = info.get("plugins", {})
    resolvers = plugins.get("resolvers", [])
    indexers = plugins.get("indexers", [])
    rankers = plugins.get("rankers", [])
    if resolvers or indexers or rankers:
        print(f"\n  {ANSI.bold}Plugins:{ANSI.reset}")
        if resolvers:
            print(f"    Resolvers: {', '.join(resolvers)}")
        if indexers:
            print(f"    Indexers:  {', '.join(indexers)}")
        if rankers:
            print(f"    Rankers:   {', '.join(rankers)}")

    print()
    return 0


def _cmd_watch(args: argparse.Namespace) -> int:
    """Handle ``rbsp watch``."""
    from rbsp.runtime.engine import RBSPEngine

    path = args.path or "."
    engine = RBSPEngine.get_instance()
    if not engine.is_initialized:
        engine.init(path)

    if engine.is_watching():
        _print_info("File watcher is already running")
        return 0

    started = engine.start_watcher(path)
    if not started:
        _print_error("Failed to start file watcher")
        return 1

    _print_success(f"Watching {Path(path).resolve()} for changes")
    _print_info("Press Ctrl+C to stop")

    # Block until interrupted
    stop = False

    def _signal_handler(signum: int, frame: object) -> None:
        nonlocal stop
        stop = True

    signal.signal(signal.SIGINT, _signal_handler)
    if hasattr(signal, "SIGTERM"):
        signal.signal(signal.SIGTERM, _signal_handler)

    try:
        while not stop:
            time.sleep(1)
    except KeyboardInterrupt:
        pass
    finally:
        engine.stop_watcher()
        print()
        _print_info("Watcher stopped")

    return 0


def _cmd_config(args: argparse.Namespace) -> int:
    """Handle ``rbsp config``."""
    from rbsp.runtime.engine import RBSPEngine

    engine = RBSPEngine.get_instance()
    if not engine.is_initialized:
        engine.init(".")

    config = engine.config

    # Show all config
    if not args.key:
        _print_header("RBSP Configuration")
        for key, value in sorted(config.to_dict().items()):
            if key == "root":
                continue
            print(f"  {ANSI.bold}{key}{ANSI.reset} = {value}")
        print()
        return 0

    # Get a specific key
    if args.key and not args.value:
        value = config.get(args.key)
        if value is None:
            _print_error(f"Unknown config key: {args.key}")
            return 1
        print(f"{args.key} = {value}")
        return 0

    # Set a key-value pair
    if args.key and args.value:
        key = args.key
        raw_value = args.value

        if not hasattr(config, key):
            _print_error(f"Unknown config key: {key}")
            return 1

        # Coerce type
        current = getattr(config, key)
        try:
            if isinstance(current, bool):
                coerced = raw_value.lower() in ("1", "true", "yes")
            elif isinstance(current, int):
                coerced = int(raw_value)
            elif isinstance(current, float):
                coerced = float(raw_value)
            elif isinstance(current, list):
                coerced = [v.strip() for v in raw_value.split(",")]
            else:
                coerced = raw_value
        except (ValueError, TypeError):
            _print_error(f"Invalid value for {key}: {raw_value}")
            return 1

        setattr(config, key, coerced)
        config.save_json()
        _print_success(f"Set {key} = {coerced}")
        return 0

    return 0


# ---------------------------------------------------------------------------
# Utilities
# ---------------------------------------------------------------------------

def _format_size(size: int) -> str:
    """Format a byte count as a human-readable string."""
    for unit in ("B", "KiB", "MiB", "GiB"):
        if size < 1024:
            return f"{size:.1f} {unit}" if isinstance(size, float) else f"{size} {unit}"
        size_f = size / 1024.0
        size = int(size_f) if unit != "GiB" else size
        if unit != "GiB":
            size = int(size_f)
            continue
        return f"{size_f:.1f} GiB"
    return f"{size} B"


# ---------------------------------------------------------------------------
# Argument parser
# ---------------------------------------------------------------------------

def build_parser() -> argparse.ArgumentParser:
    """Build the CLI argument parser."""
    parser = argparse.ArgumentParser(
        prog="rbsp",
        description="RBSP - Resonance-Based Semantic Protocol for ultra-fast search",
    )
    parser.add_argument(
        "--version", action="version", version="rbsp 0.1.0",
    )
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # init
    p_init = subparsers.add_parser("init", help="Initialize RBSP in a directory")
    p_init.add_argument("path", nargs="?", default=".", help="Project root (default: .)")

    # index
    p_index = subparsers.add_parser("index", help="Index a directory")
    p_index.add_argument("path", nargs="?", default=".", help="Directory to index (default: .)")
    p_index.add_argument("--force", "-f", action="store_true", help="Force re-index all files")

    # search
    p_search = subparsers.add_parser("search", help="Search indexed content")
    p_search.add_argument("query", nargs="+", help="Search query")
    p_search.add_argument("--scope", "-s", default=".", help="Search scope directory")
    p_search.add_argument("--limit", "-n", type=int, default=20, help="Max results (default: 20)")
    p_search.add_argument("--verbose", "-v", action="store_true", help="Show snippets")
    p_search.add_argument("--json", "-j", action="store_true", help="Output as JSON")

    # status
    p_status = subparsers.add_parser("status", help="Show index status and statistics")
    p_status.add_argument("path", nargs="?", default=".", help="Project root (default: .)")

    # watch
    p_watch = subparsers.add_parser("watch", help="Start file watcher for live indexing")
    p_watch.add_argument("path", nargs="?", default=".", help="Directory to watch (default: .)")

    # config
    p_config = subparsers.add_parser("config", help="Manage configuration")
    p_config.add_argument("key", nargs="?", help="Config key to get/set")
    p_config.add_argument("value", nargs="?", help="Value to set")

    return parser


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------

def main(argv: Optional[Sequence[str]] = None) -> int:
    """CLI entry point."""
    parser = build_parser()
    args = parser.parse_args(argv)

    if not args.command:
        parser.print_help()
        return 0

    handlers = {
        "init": _cmd_init,
        "index": _cmd_index,
        "search": _cmd_search,
        "status": _cmd_status,
        "watch": _cmd_watch,
        "config": _cmd_config,
    }

    handler = handlers.get(args.command)
    if handler is None:
        parser.print_help()
        return 1

    try:
        return handler(args)
    except KeyboardInterrupt:
        print()
        return 130
    except Exception as exc:
        _print_error(str(exc))
        return 1


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