"""Minimal interactive dashboard for bare `armorer` (no subcommand)."""

from __future__ import annotations

import os
import json
import sys
from typing import Callable, List

import questionary
from rich.panel import Panel
from rich.table import Table

from armorer.catalog import all_known_agents
from armorer.config import ConfigManager
from armorer.cli import common as cli_common
from armorer.cli.core_lifecycle import (
    list_running_host_core_pids,
    list_running_telegram_gateway_pids,
)
from armorer.cli import registry_helpers as cli_registry
from armorer.registry import AppRegistry


def _invoke_cli(args: List[str]) -> int:
    """Run another Armorer CLI invocation in-process (same as `armorer ...`)."""
    from typer.testing import CliRunner

    from armorer.main import app

    result = CliRunner().invoke(app, args, catch_exceptions=False, prog_name="armorer")
    if result.exit_code and result.exit_code != 0:
        cli_common.console.print(f"[dim]exit code: {result.exit_code}[/dim]")
    out = (result.stdout or "").strip()
    err = (result.stderr or "").strip()
    if out:
        cli_common.console.print(out)
    if err:
        cli_common.console.print(err)
    return int(result.exit_code or 0)


def _installed_aliases() -> List[str]:
    registry = AppRegistry()
    apps = cli_registry.list_apps_with_openclaw_fallback(registry)
    return sorted({str(a.name).strip() for a in apps if str(a.name).strip()})


def _docker_mode_heuristic() -> str:
    cfg = ConfigManager()
    inside_container = bool(getattr(cfg, "is_docker", False))

    # Heuristic: if we can talk to the Docker daemon, the installed agents will
    # almost certainly be driven via docker compose stacks.
    # probe_docker_daemon returns None on success, otherwise details.
    docker_ok = cli_common.probe_docker_daemon(detail_limit=120) is None

    if docker_ok:
        base = "Docker enabled"
    else:
        base = "Docker unavailable (local-only)"

    if inside_container:
        return f"{base} (running in container)"
    return base


def _llm_status() -> str:
    cm = ConfigManager()
    provider = (cm.get_key("MODEL_PROVIDER") or "").strip().lower()
    model = (cm.get_key("Armorer_MODEL") or "").strip()
    if not provider:
        return "LLM: not configured"

    key_map = {
        "openai": "OPENAI_API_KEY",
        "anthropic": "ANTHROPIC_API_KEY",
        "gemini": "GEMINI_API_KEY",
        "openrouter": "OPENROUTER_API_KEY",
    }
    required_key = key_map.get(provider)
    if not required_key:
        # Other providers are supported elsewhere; we just show provider set.
        return f"LLM: {provider} (key check skipped)"

    has_key = bool((cm.get_key(required_key) or "").strip())
    if has_key:
        suffix = f" ({model})" if model else ""
        return f"LLM: {provider} configured{suffix}"
    return f"LLM: {provider} (missing {required_key})"


def _gateway_status() -> str:
    host_core = bool(list_running_host_core_pids())
    telegram_gateway = bool(list_running_telegram_gateway_pids())

    if telegram_gateway and host_core:
        return "Gateway/Core: running"
    if telegram_gateway:
        return "Gateway: running (core not detected)"
    if host_core:
        return "Core: running (gateway not detected)"
    return "Gateway/Core: not running"


def _alerts_status() -> str:
    config_dir = cli_common.armorer_config_dir()
    settings_path = config_dir / "guard_settings.json"
    alert_cache_path = config_dir / "guard_alert_cache.json"

    # Read JSON directly to keep the dashboard lightweight.
    guard_settings: dict = {}
    try:
        if settings_path.exists():
            raw = settings_path.read_text(encoding="utf-8")
            parsed = json.loads(raw)
            if isinstance(parsed, dict):
                guard_settings = parsed
    except Exception:
        guard_settings = {}

    alerting_enabled = bool(guard_settings.get("alerting_enabled", True))
    proactive_enabled = bool(guard_settings.get("proactive_security_enabled", False))
    alert_chat = str(guard_settings.get("telegram_alert_chat_id") or "").strip()

    # Approximation of "any alerts": count recent cache fingerprints.
    recent_count = 0
    latest_ts = None
    try:
        if alert_cache_path.exists():
            raw = alert_cache_path.read_text(encoding="utf-8")
            cache = json.loads(raw)
            if isinstance(cache, dict):
                for _fp, ts in cache.items():
                    try:
                        fts = float(ts)
                    except Exception:
                        continue
                    recent_count += 1
                    latest_ts = fts if latest_ts is None else max(latest_ts, fts)
    except Exception:
        pass

    recent_note = "none"
    if recent_count:
        recent_note = f"{recent_count} (latest {int(latest_ts)})" if latest_ts else str(recent_count)

    if proactive_enabled and alerting_enabled and alert_chat:
        return f"Alerts: enabled (chat {alert_chat}) | recent: {recent_note}"
    if alerting_enabled and proactive_enabled:
        return f"Alerts: enabled (chat unset) | recent: {recent_note}"
    return f"Alerts: disabled | recent: {recent_note}"


def _status_panel() -> Panel:
    docker_mode = _docker_mode_heuristic()
    llm = _llm_status()
    gateway = _gateway_status()
    alerts = _alerts_status()

    registry = AppRegistry()
    apps = registry.list_apps()
    installed = len(apps or [])

    table = Table.grid(expand=True)
    table.add_row(f"[bold]Runtime[/bold]: {docker_mode}")
    table.add_row(f"[bold]LLM[/bold]: {llm}")
    table.add_row(f"[bold]Gateway[/bold]: {gateway}")
    table.add_row(f"[bold]Alerts[/bold]: {alerts}")
    table.add_row(f"[bold]Installed[/bold]: {installed} agent(s)")

    return Panel(table, title="Armorer Status", border_style="cyan", expand=True)


def run_interactive_loop(
    *,
    is_ignorable_cli_exit_fn: Callable[[str, str], bool],
    print_logo: Callable[[], None],
    config_impl: Callable[[], None],
    print_help_text: Callable[[], None],
) -> None:
    """TTY menu loop; returns when user chooses Exit."""
    print_logo()

    main_choices = [
        "Refresh status",
        "View status (quick)",
        "List installed agents",
        "Install an agent (catalog)",
        "Run / check status",
        "Armorer model & settings (config)",
        "Configure an installed app",
        "Show common CLI commands",
        "Exit",
    ]

    while True:
        cli_common.console.print(_status_panel())
        choice = questionary.select(
            "Armorer — what would you like to do?",
            choices=main_choices,
            default=main_choices[0],
        ).ask()

        if not choice or choice == "Exit":
            cli_common.console.print("[dim]Goodbye. Tip: run `armorer --help` for full command reference.[/dim]")
            return

        if choice == "Refresh status":
            continue
        if choice == "View status (quick)":
            cli_common.console.print(_status_panel())
            questionary.press_any_key_to_continue("Press any key to continue...").ask()
            continue

        if choice == "List installed agents":
            from armorer.cli.install import list_apps_impl

            list_apps_impl(is_ignorable_cli_exit_fn=is_ignorable_cli_exit_fn)
            questionary.press_any_key_to_continue("Press any key to continue...").ask()

        elif choice == "Install an agent (catalog)":
            agents = [a for a in all_known_agents() if (a.id or "").strip()]
            if not agents:
                cli_common.console.print("[yellow]No agents in catalog.[/yellow]")
                continue
            agents_sorted = sorted(agents, key=lambda x: x.id.lower())
            labels = [f"{a.id} ({a.aliases[0]})" if a.aliases else a.id for a in agents_sorted]
            pick = questionary.select(
                "Choose an agent to install:",
                choices=labels,
            ).ask()
            if not pick:
                continue
            idx = labels.index(pick)
            agent_id = agents_sorted[idx].id
            if not questionary.confirm(
                f"Install {agent_id} with --yes --skip-scan?",
                default=True,
            ).ask():
                continue
            _invoke_cli(["install", agent_id, "--yes", "--skip-scan"])
            questionary.press_any_key_to_continue("Press any key to continue...").ask()

        elif choice == "Run / check status":
            aliases = _installed_aliases()
            if not aliases:
                cli_common.console.print("[yellow]No agents installed yet. Use Install first.[/yellow]")
                continue
            alias = questionary.select("Which instance?", choices=aliases).ask()
            if not alias:
                continue
            action = questionary.select(
                "Action:",
                choices=["status", "run (interactive menu)", "doctor (if available)"],
            ).ask()
            if not action:
                continue
            if action.startswith("status"):
                _invoke_cli(["run", alias, "status"])
            elif action.startswith("run"):
                _invoke_cli(["run", alias])
            else:
                _invoke_cli(["run", alias, "doctor"])
            questionary.press_any_key_to_continue("Press any key to continue...").ask()

        elif choice == "Armorer model & settings (config)":
            config_impl()
            questionary.press_any_key_to_continue("Press any key to continue...").ask()

        elif choice == "Configure an installed app":
            aliases = _installed_aliases()
            if not aliases:
                cli_common.console.print("[yellow]No agents installed yet.[/yellow]")
                continue
            alias = questionary.select("Which app to configure?", choices=aliases).ask()
            if not alias:
                continue
            _invoke_cli(["app", "configure", alias])
            questionary.press_any_key_to_continue("Press any key to continue...").ask()

        elif choice == "Show common CLI commands":
            _show_common_commands_table()
            print_help_text()
            questionary.press_any_key_to_continue("Press any key to continue...").ask()


def _show_common_commands_table() -> None:
    table = Table(title="Common commands", border_style="cyan", show_lines=True)
    table.add_column("Command", style="bold", no_wrap=True)
    table.add_column("Description")
    rows = [
        ("armorer list", "Installed agents and status"),
        ("armorer install <agent> --yes --skip-scan", "Install from catalog or URL"),
        ("armorer run <alias>", "Run / manage an instance"),
        ("armorer app configure <alias>", "Configure an installed app"),
        ("armorer config", "Model provider, API keys, guard, Langfuse"),
        ("armorer core …", "Armorer Core service"),
        ("armorer gateway …", "Telegram / messaging gateways"),
        ("armorer scan / uninstall / cleanup", "Security audit, remove, cleanup"),
    ]
    for cmd, desc in rows:
        table.add_row(cmd, desc)
    cli_common.console.print(Panel(table, border_style="dim"))


def should_use_dashboard() -> bool:
    # Use os.environ (not sys.environ) so tests/tools that stub `sys` still work.
    if str(os.environ.get("ARMORER_DISABLE_DASHBOARD", "")).strip().lower() in (
        "1",
        "true",
        "yes",
        "on",
    ):
        return False
    try:
        return bool(sys.stdin.isatty() and sys.stdout.isatty())
    except Exception:
        return False
