"""
CLI commands for the Inbox Router Agent.

Provides three commands:
  navig inbox process-current <file>  — process a single inbox file
  navig inbox process-all             — process all inbox .md files
  navig inbox dry-run                 — preview routing without writing

All commands use the InboxRouterAgent and execute_plan from
navig.agents.inbox_router.
"""

from __future__ import annotations

import json
import logging
from pathlib import Path

import typer

logger = logging.getLogger("navig.commands.inbox")

inbox_app = typer.Typer(
    name="inbox",
    help="Inbox Router — classify and route .navig/plans/inbox/ files",
    invoke_without_command=True,
    no_args_is_help=False,
)


@inbox_app.callback()
def _inbox_callback(ctx: typer.Context) -> None:
    if ctx.invoked_subcommand is None:
        import os as _os  # noqa: PLC0415

        if _os.environ.get("NAVIG_LAUNCHER", "fuzzy") == "legacy":
            print(ctx.get_help())
            raise typer.Exit()
        from navig.cli.launcher import smart_launch  # noqa: PLC0415

        smart_launch("inbox", inbox_app)


def _find_project_root() -> Path:
    """Walk up from cwd to find a directory containing .navig/."""
    cwd = Path.cwd()
    for parent in [cwd, *cwd.parents]:
        if (parent / ".navig").is_dir():
            return parent
    # Fallback to cwd
    return cwd


def _print_plan(plan: dict, verbose: bool = False) -> None:
    """Pretty-print a single plan result."""
    source = Path(plan.get("source_file", "?")).name
    ctype = plan.get("content_type", "?")
    confidence = plan.get("confidence", "?")
    target = plan.get("target_path") or "(stays in inbox)"
    space = plan.get("space") or "life"
    space_source = plan.get("space_source") or "default"
    rationale = plan.get("rationale", "")

    status_icon = {
        "task_roadmap": "[PLAN]",
        "brief": "[BRIEF]",
        "wiki_knowledge": "[WIKI]",
        "memory_log": "[MEM]",
        "other": "[?]",
    }.get(ctype, "[?]")

    typer.echo(f"  {status_icon} {source}")
    typer.echo(f"    Type: {ctype}  Confidence: {confidence}")
    typer.echo(f"    Space: {space}  Source: {space_source}")
    typer.echo(f"    Target: {target}")
    if rationale:
        typer.echo(f"    Rationale: {rationale}")
    if plan.get("error"):
        typer.secho(f"    Error: {plan['error']}", fg=typer.colors.RED)
    typer.echo("")


def _print_execution_result(result: dict) -> None:
    """Pretty-print an execution result."""
    status = result.get("status", "?")
    source = Path(result.get("source", "?")).name

    colors = {
        "written": typer.colors.GREEN,
        "dry_run": typer.colors.YELLOW,
        "kept_in_inbox": typer.colors.CYAN,
        "error": typer.colors.RED,
        "skipped": typer.colors.WHITE,
    }
    color = colors.get(status, typer.colors.WHITE)

    typer.secho(f"  [{status.upper()}] {source}", fg=color)
    if result.get("target"):
        typer.echo(f"    -> {result['target']}")
    if result.get("would_write"):
        typer.echo(f"    -> (would write) {result['would_write']}")
    if result.get("source_moved"):
        typer.echo(f"    Source moved to: {result['source_moved']}")
    if result.get("reason"):
        typer.echo(f"    Reason: {result['reason']}")
    if result.get("error"):
        typer.secho(f"    Error: {result['error']}", fg=typer.colors.RED)


@inbox_app.command("process-current")
def process_current(
    file: str = typer.Argument(..., help="Path to a single inbox .md file"),
    no_llm: bool = typer.Option(False, "--no-llm", help="Use heuristic only (no LLM)"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Preview without writing"),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
    no_move: bool = typer.Option(False, "--no-move", help="Don't move source after routing"),
    space: str | None = typer.Option(
        None,
        "--space",
        help="Manual space tag override (wins over frontmatter/classifier)",
    ),
    backend: str = typer.Option(
        "cli_llm", "--backend", "-b", help="Backend caller (cli_llm or vscode_copilot)"
    ),
) -> None:
    """Process a single inbox file — classify, transform, and route."""
    from navig.agents.inbox_router import InboxRouterAgent, execute_plan

    file_path = Path(file).resolve()
    if not file_path.exists():
        typer.secho(f"File not found: {file}", fg=typer.colors.RED)
        raise typer.Exit(1)

    project_root = _find_project_root()
    agent = InboxRouterAgent(project_root, use_llm=not no_llm, backend=backend)

    typer.echo(f"Processing: {file_path.name}")
    plan = agent.process_single(file_path, dry_run=dry_run, manual_space=space)

    if json_output:
        typer.echo(json.dumps(plan, indent=2, default=str))
        return

    _print_plan(plan)

    if not dry_run and not plan.get("error"):
        result = execute_plan(project_root, plan, dry_run=False, move_source=not no_move)
        _print_execution_result(result)
    elif dry_run:
        result = execute_plan(project_root, plan, dry_run=True)
        _print_execution_result(result)


@inbox_app.command("process-all")
def process_all(
    no_llm: bool = typer.Option(False, "--no-llm", help="Use heuristic only (no LLM)"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Preview without writing"),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
    no_move: bool = typer.Option(False, "--no-move", help="Don't move source after routing"),
    space: str | None = typer.Option(
        None,
        "--space",
        help="Manual space tag override applied to all files",
    ),
    backend: str = typer.Option(
        "cli_llm", "--backend", "-b", help="Backend caller (cli_llm or vscode_copilot)"
    ),
) -> None:
    """Process ALL .md files in .navig/plans/inbox/."""
    from navig.agents.inbox_router import (
        InboxRouterAgent,
        execute_plan,
        list_inbox_files,
    )

    project_root = _find_project_root()
    files = list_inbox_files(project_root)

    if not files:
        typer.secho("No inbox files found in .navig/plans/inbox/", fg=typer.colors.YELLOW)
        raise typer.Exit(0)

    typer.echo(f"Found {len(files)} inbox file(s)\n")

    agent = InboxRouterAgent(project_root, use_llm=not no_llm, backend=backend)
    plans = agent.process_batch(files, dry_run=dry_run, manual_space=space)

    if json_output:
        typer.echo(json.dumps(plans, indent=2, default=str))
        return

    results = []
    for plan in plans:
        _print_plan(plan)
        if not dry_run and not plan.get("error"):
            result = execute_plan(project_root, plan, dry_run=False, move_source=not no_move)
        else:
            result = execute_plan(project_root, plan, dry_run=True)
        results.append(result)
        _print_execution_result(result)

    # Summary
    written = sum(1 for r in results if r.get("status") == "written")
    kept = sum(1 for r in results if r.get("status") == "kept_in_inbox")
    errors = sum(1 for r in results if r.get("status") == "error")
    typer.echo(f"\nSummary: {written} routed, {kept} kept in inbox, {errors} errors")


@inbox_app.command("dry-run")
def dry_run(
    no_llm: bool = typer.Option(False, "--no-llm", help="Use heuristic only (no LLM)"),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
    space: str | None = typer.Option(
        None,
        "--space",
        help="Manual space tag override applied to all files",
    ),
    backend: str = typer.Option(
        "cli_llm", "--backend", "-b", help="Backend caller (cli_llm or vscode_copilot)"
    ),
) -> None:
    """Preview routing for all inbox files (no files written or moved)."""
    from navig.agents.inbox_router import (
        InboxRouterAgent,
        execute_plan,
        list_inbox_files,
    )

    project_root = _find_project_root()
    files = list_inbox_files(project_root)

    if not files:
        typer.secho("No inbox files found in .navig/plans/inbox/", fg=typer.colors.YELLOW)
        raise typer.Exit(0)

    typer.echo(f"Dry-run preview for {len(files)} inbox file(s)\n")

    agent = InboxRouterAgent(project_root, use_llm=not no_llm, backend=backend)
    plans = agent.process_batch(files, dry_run=True, manual_space=space)

    if json_output:
        typer.echo(json.dumps(plans, indent=2, default=str))
        return

    for plan in plans:
        _print_plan(plan, verbose=True)
        result = execute_plan(project_root, plan, dry_run=True)
        _print_execution_result(result)

    # Summary
    types_count = {}
    for p in plans:
        ct = p.get("content_type", "other")
        types_count[ct] = types_count.get(ct, 0) + 1
    typer.echo("\nClassification summary:")
    for ct, count in sorted(types_count.items()):
        typer.echo(f"  {ct}: {count}")


@inbox_app.command("filter")
def filter_cmd(
    path: str | None = typer.Option(
        None,
        "--path",
        "-p",
        help="Project root (default: auto-detected from cwd)",
    ),
    dry_run: bool = typer.Option(False, "--dry-run", help="Preview only — no files written"),
    watch: bool = typer.Option(
        False, "--watch", "-w", help="Keep running and re-filter on changes"
    ),
    interval: float = typer.Option(5.0, "--interval", "-i", help="Watch interval in seconds"),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """Filter and normalize all .navig/**/*.md files in-place.

    Adds missing YAML frontmatter, normalizes heading structure, and writes
    each file back.  Files inside .navig/plans/inbox/ are always skipped —
    they are router inputs, not processed documents.

    \b
    Examples:
      navig inbox filter
      navig inbox filter --dry-run
      navig inbox filter --watch --interval 10
    """
    from navig.agents.filtering_engine import FilteringEngine

    project_root = Path(path).resolve() if path else _find_project_root()

    if not (project_root / ".navig").is_dir():
        typer.secho(
            f"No .navig/ directory found at {project_root}",
            fg=typer.colors.RED,
        )
        raise typer.Exit(1)

    engine = FilteringEngine(project_root)

    if watch:
        typer.secho(
            f"Watching .navig/ under {project_root} (interval={interval}s) — Ctrl+C to stop",
            fg=typer.colors.CYAN,
        )
        try:
            engine.start_watch(interval_secs=interval, dry_run=dry_run)
        except KeyboardInterrupt:
            typer.echo("\nWatch stopped.")
        return

    # Single-pass scan
    typer.echo(f"Filtering .navig/ under {project_root} {'[dry-run]' if dry_run else ''}\n")
    results = engine.scan_and_filter(dry_run=dry_run)

    if json_output:
        typer.echo(
            __import__("json").dumps(
                [
                    {
                        "path": str(r.path),
                        "changed": r.changed,
                        "would_change": r.would_change,
                        "skipped": r.skipped,
                        "error": r.error,
                        "rules_applied": r.rules_applied,
                    }
                    for r in results
                ],
                indent=2,
            )
        )
        return

    if not results:
        typer.secho("All files are already clean — nothing to do.", fg=typer.colors.GREEN)
        return

    changed = 0
    would_change = 0
    errors = 0

    for r in results:
        name = r.path.name
        if r.error:
            typer.secho(f"  [ERROR] {name}: {r.error}", fg=typer.colors.RED)
            errors += 1
        elif r.changed:
            typer.secho(f"  [UPDATED] {name}  rules={r.rules_applied}", fg=typer.colors.GREEN)
            changed += 1
        elif r.would_change:
            typer.secho(
                f"  [WOULD UPDATE] {name}  rules={r.rules_applied}",
                fg=typer.colors.YELLOW,
            )
            would_change += 1

    if dry_run:
        typer.echo(f"\nDry-run: {would_change} would be updated, {errors} errors.")
    else:
        typer.echo(f"\nDone: {changed} updated, {errors} errors.")


@inbox_app.command("watch")
def watch_cmd(
    path: str | None = typer.Option(
        None,
        "--path",
        "-p",
        help="Project root (default: auto-detected from cwd)",
    ),
    interval: float = typer.Option(5.0, "--interval", "-i", help="Poll interval in seconds"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Detect and report changes only"),
) -> None:
    """Watch .navig/**/*.md for changes and re-filter automatically.

    Alias for: navig inbox filter --watch

    \b
    Examples:
      navig inbox watch
      navig inbox watch --interval 10 --dry-run
    """
    from navig.agents.filtering_engine import FilteringEngine

    project_root = Path(path).resolve() if path else _find_project_root()

    if not (project_root / ".navig").is_dir():
        typer.secho(
            f"No .navig/ directory found at {project_root}",
            fg=typer.colors.RED,
        )
        raise typer.Exit(1)

    engine = FilteringEngine(project_root)
    typer.secho(
        f"Watching .navig/ under {project_root} (interval={interval}s) — Ctrl+C to stop",
        fg=typer.colors.CYAN,
    )
    try:
        engine.start_watch(interval_secs=interval, dry_run=dry_run)
    except KeyboardInterrupt:
        typer.echo("\nWatch stopped.")


@inbox_app.command("stats")
def stats_cmd(
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """Show routing summary statistics from the inbox SQLite store."""
    from navig.inbox.store import InboxStore

    store = InboxStore()
    data = store.stats()

    if json_output:
        typer.echo(json.dumps(data, indent=2))
        return

    total = data.get("total_events", 0)
    by_status = data.get("by_status", {})
    by_category = data.get("by_category", {})

    typer.secho("Inbox Statistics", fg=typer.colors.CYAN, bold=True)
    typer.echo(f"  Total events  : {total}")
    typer.echo("")
    typer.secho("  By status:", fg=typer.colors.WHITE)
    for status, count in sorted(by_status.items()):
        color = typer.colors.GREEN if status == "routed" else typer.colors.YELLOW
        typer.secho(f"    {status:<12} {count}", fg=color)
    typer.echo("")
    typer.secho("  By category:", fg=typer.colors.WHITE)
    for category, count in sorted(by_category.items(), key=lambda x: -x[1]):
        typer.echo(f"    {category:<26} {count}")


@inbox_app.command("add")
def add_url_cmd(
    url: str = typer.Argument(..., help="URL to fetch, classify, and route"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Preview without writing"),
    no_llm: bool = typer.Option(False, "--no-llm", help="Use BM25 classifier only"),
    mode: str = typer.Option("copy", "--mode", "-m", help="Route mode: copy | move | link"),
    space: str | None = typer.Option(
        None,
        "--space",
        help="Manual space tag for this intake item",
    ),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """Fetch a URL, classify it, and route it into the wiki inbox.

    \b
    Examples:
      navig inbox add https://example.com/article
      navig inbox add https://arxiv.org/abs/2401.00001 --mode move
    """
    import hashlib
    import re
    import time
    import urllib.error
    import urllib.request

    from navig.inbox.classifier import Classifier
    from navig.inbox.router import InboxRouter, RouteMode
    from navig.inbox.store import InboxEvent, InboxStore, RoutingDecision
    from navig.spaces import normalize_space_name

    typer.echo(f"Fetching: {url}")

    # Fetch content
    try:
        req = urllib.request.Request(url, headers={"User-Agent": "NAVIG/1.0"})
        with urllib.request.urlopen(req, timeout=15) as resp:
            raw = resp.read(204800)  # cap at 200 KB
            content_type = resp.headers.get("content-type", "")
            content = raw.decode("utf-8", errors="replace")
    except Exception as exc:
        typer.secho(f"Fetch failed: {exc}", fg=typer.colors.RED)
        raise typer.Exit(1) from exc

    # Derive a filename from URL
    path_part = url.rstrip("/").split("/")[-1] or "index"
    path_part = re.sub(r"[^a-zA-Z0-9_-]", "_", path_part)
    filename = f"{path_part}.md"

    # Strip HTML tags for classification
    text = re.sub(r"<[^>]+>", " ", content)
    text = re.sub(r"\s+", " ", text).strip()

    # Classify
    classifier = Classifier(use_llm=not no_llm)
    result = classifier.classify(text, filename=filename, extra_context=url)

    if json_output:
        typer.echo(
            json.dumps(
                {
                    "url": url,
                    "filename": filename,
                    "category": result.category,
                    "confidence": result.confidence,
                    "method": result.method,
                },
                indent=2,
            )
        )
        if dry_run:
            return

    typer.echo(f"  Category  : {result.category}")
    typer.echo(f"  Confidence: {result.confidence:.2%}  ({result.method})")

    # Build markdown content
    selected_space = normalize_space_name(space)
    md = (
        f"---\n"
        f"space: {selected_space}\n"
        f"tags: [{selected_space}]\n"
        f"source: {url}\n"
        f"fetched_at: {time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())}\n"
        f"category: {result.category}\n"
        f"confidence: {result.confidence}\n"
        f"---\n\n"
        f"# {path_part}\n\n"
        f"Source: {url}\n\n"
        f"{text[:8000]}\n"
    )

    # Route
    project_root = _find_project_root()
    router_mode = RouteMode(mode) if mode in ("copy", "move", "link") else RouteMode.COPY
    router = InboxRouter(project_root=project_root, mode=router_mode)
    route_result = router.route_url(url, md, filename, result, dry_run=dry_run)

    if json_output:
        typer.echo(
            json.dumps(
                {
                    "route_status": route_result.status,
                    "destination": route_result.destination,
                    "result_path": route_result.result_path,
                },
                indent=2,
            )
        )
        return

    if route_result.status == "routed":
        dest = route_result.result_path or route_result.destination
        if dry_run:
            typer.secho(f"  [dry-run] Would write → {dest}", fg=typer.colors.YELLOW)
        else:
            typer.secho(f"  ✓ Routed  → {dest}", fg=typer.colors.GREEN)
            # Persist to store
            store = InboxStore()
            event = InboxEvent(
                source_path=url,
                source_type="url",
                filename=filename,
                size_bytes=len(md.encode()),
                content_hash=hashlib.sha256(md.encode()).hexdigest()[:16],
                status="routed",
            )
            event_id = store.insert_event(event)
            decision = RoutingDecision(
                event_id=event_id,
                category=result.category,
                confidence=result.confidence,
                mode=mode,
                destination=route_result.destination or "",
                result_path=route_result.result_path,
                executed=True,
                classifier=result.method,
            )
            store.insert_decision(decision)
    else:
        typer.secho(
            f"  {route_result.status}: {route_result.error or 'no destination'}",
            fg=typer.colors.YELLOW,
        )


@inbox_app.command("ui")
def ui_cmd(
    path: str | None = typer.Option(
        None, "--path", "-p", help="Project root (default: auto-detected)"
    ),
    no_llm: bool = typer.Option(False, "--no-llm", help="BM25 only, no LLM"),
    mode: str = typer.Option("copy", "--mode", "-m", help="Route mode: copy | move | link"),
) -> None:
    """Interactive TUI review panel — inspect inbox files and approve routing.

    Shows each pending file with its classification result. Press:
      [y] Route now   [n] Keep in inbox   [q] Quit   [?] Details
    """
    from navig.inbox.classifier import Classifier
    from navig.inbox.router import InboxRouter, RouteMode
    from navig.inbox.store import InboxEvent, InboxStore, RoutingDecision

    project_root = Path(path).resolve() if path else _find_project_root()
    inbox_dir = project_root / ".navig" / "wiki" / "inbox"
    inbox_dir.mkdir(parents=True, exist_ok=True)

    files = [f for f in inbox_dir.iterdir() if f.is_file() and not f.name.startswith(".")]
    if not files:
        # Also check global inbox
        try:
            from navig.platform.paths import navig_data_dir

            global_inbox = navig_data_dir() / "inbox"
        except Exception:
            global_inbox = Path.home() / ".navig" / "inbox"
        if global_inbox.is_dir():
            files += [
                f for f in global_inbox.iterdir() if f.is_file() and not f.name.startswith(".")
            ]

    if not files:
        typer.secho("No inbox files found.", fg=typer.colors.YELLOW)
        return

    classifier = Classifier(use_llm=not no_llm)
    router_mode = RouteMode(mode) if mode in ("copy", "move", "link") else RouteMode.COPY
    router = InboxRouter(project_root=project_root, mode=router_mode)
    store = InboxStore()

    typer.secho(
        f"NAVIG Inbox Review  ({len(files)} file(s))  [y=route  n=skip  q=quit  ?=details]",
        fg=typer.colors.CYAN,
        bold=True,
    )
    typer.echo("")

    routed = skipped = 0
    for i, f in enumerate(files, 1):
        content = ""
        try:
            content = f.read_text(encoding="utf-8", errors="replace")
        except Exception:  # noqa: BLE001
            pass  # best-effort; failure is non-critical
        result = classifier.classify(content, filename=f.name)

        typer.secho(f"[{i}/{len(files)}] {f.name}", fg=typer.colors.WHITE, bold=True)
        typer.echo(f"  Category  : {result.category}")
        typer.echo(f"  Confidence: {result.confidence:.2%}  ({result.method})")
        typer.echo(f"  Space     : {selected_space}")

        while True:
            choice = typer.prompt("  → route? [y/n/q/?]", default="y").strip().lower()
            if choice == "?":
                typer.echo(f"  Explanation: {result.explanation}")
                if result.alternatives:
                    typer.echo("  Alternatives:")
                    for alt_cat, alt_score in result.alternatives:
                        typer.echo(f"    {alt_cat}: {alt_score:.4f}")
                continue
            break

        if choice == "q":
            typer.echo("Quit.")
            break
        elif choice == "y":
            route_result = router.route(f, result, dry_run=False)
            if route_result.status == "routed":
                typer.secho(f"  ✓ → {route_result.result_path}", fg=typer.colors.GREEN)
                routed += 1
                # Persist
                import hashlib

                event = InboxEvent(
                    source_path=str(f),
                    source_type="file",
                    filename=f.name,
                    size_bytes=f.stat().st_size,
                    content_hash=hashlib.sha256(content.encode()).hexdigest()[:16],
                    status="routed",
                )
                event_id = store.insert_event(event)
                decision = RoutingDecision(
                    event_id=event_id,
                    category=result.category,
                    confidence=result.confidence,
                    mode=router_mode.value,
                    destination=route_result.destination or "",
                    result_path=route_result.result_path,
                    executed=True,
                    classifier=result.method,
                )
                store.insert_decision(decision)
            else:
                typer.secho(
                    f"  ✗ {route_result.status}: {route_result.error}",
                    fg=typer.colors.RED,
                )
        else:
            typer.secho("  Kept in inbox.", fg=typer.colors.YELLOW)
            skipped += 1
        typer.echo("")

    typer.secho(f"Done. {routed} routed, {skipped} kept.", fg=typer.colors.CYAN)
