from __future__ import annotations

import io
import sys
from pathlib import Path

import pytest

from hermes_cli.console_engine import HermesConsoleEngine, run_console_repl


EXPECTED_CONSOLE_COMMANDS = {
    ("status",),
    ("doctor",),
    ("logs",),
    ("version",),
    ("dump",),
    ("debug", "share"),
    ("debug", "delete"),
    ("prompt-size",),
    ("insights",),
    ("security", "audit"),
    ("portal", "info"),
    ("portal", "tools"),
    ("backup",),
    ("import",),
    ("send",),
    ("config", "show"),
    ("config", "path"),
    ("config", "env-path"),
    ("config", "check"),
    ("config", "migrate"),
    ("config", "set"),
    ("sessions", "list"),
    ("sessions", "stats"),
    ("sessions", "export"),
    ("sessions", "rename"),
    ("sessions", "optimize"),
    ("sessions", "repair"),
    ("cron", "list"),
    ("cron", "status"),
    ("cron", "create"),
    ("cron", "edit"),
    ("cron", "pause"),
    ("cron", "resume"),
    ("cron", "run"),
    ("cron", "remove"),
    ("cron", "tick"),
    ("profile",),
    ("profile", "list"),
    ("profile", "show"),
    ("profile", "info"),
    ("profile", "create"),
    ("profile", "use"),
    ("profile", "describe"),
    ("profile", "rename"),
    ("profile", "delete"),
    ("profile", "export"),
    ("profile", "import"),
    ("profile", "install"),
    ("profile", "update"),
    ("tools", "list"),
    ("tools", "enable"),
    ("tools", "disable"),
    ("tools", "post-setup"),
    ("plugins", "list"),
    ("plugins", "enable"),
    ("plugins", "disable"),
    ("plugins", "install"),
    ("plugins", "update"),
    ("plugins", "remove"),
    ("skills", "browse"),
    ("skills", "search"),
    ("skills", "inspect"),
    ("skills", "list"),
    ("skills", "check"),
    ("skills", "list-modified"),
    ("skills", "diff"),
    ("skills", "install"),
    ("skills", "update"),
    ("skills", "audit"),
    ("skills", "uninstall"),
    ("skills", "reset"),
    ("skills", "opt-in"),
    ("skills", "opt-out"),
    ("skills", "repair-official"),
    ("skills", "snapshot", "export"),
    ("skills", "snapshot", "import"),
    ("skills", "tap", "list"),
    ("skills", "tap", "add"),
    ("skills", "tap", "remove"),
    ("mcp", "list"),
    ("mcp", "catalog"),
    ("mcp", "test"),
    ("mcp", "add"),
    ("mcp", "remove"),
    ("mcp", "install"),
    ("mcp", "login"),
    ("mcp", "reauth"),
    ("mcp", "configure"),
    ("mcp", "picker"),
    ("memory", "status"),
    ("memory", "off"),
    ("memory", "reset"),
    ("auth", "list"),
    ("auth", "status"),
    ("auth", "reset"),
    ("auth", "add"),
    ("auth", "remove"),
    ("auth", "logout"),
    ("auth", "spotify", "status"),
    ("auth", "spotify", "login"),
    ("auth", "spotify", "logout"),
    ("pairing", "list"),
    ("pairing", "approve"),
    ("pairing", "revoke"),
    ("pairing", "clear-pending"),
    ("webhook", "list"),
    ("webhook", "subscribe"),
    ("webhook", "remove"),
    ("webhook", "test"),
    ("hooks", "list"),
    ("hooks", "test"),
    ("hooks", "doctor"),
    ("hooks", "revoke"),
    ("slack", "manifest"),
    ("project", "list"),
    ("project", "show"),
    ("project", "create"),
    ("project", "add-folder"),
    ("project", "remove-folder"),
    ("project", "rename"),
    ("project", "set-primary"),
    ("project", "use"),
    ("project", "archive"),
    ("project", "restore"),
    ("project", "bind-board"),
    ("kanban", "init"),
    ("kanban", "boards", "list"),
    ("kanban", "boards", "create"),
    ("kanban", "boards", "rm"),
    ("kanban", "boards", "switch"),
    ("kanban", "boards", "current"),
    ("kanban", "boards", "rename"),
    ("kanban", "boards", "set-workdir"),
    ("kanban", "create"),
    ("kanban", "list"),
    ("kanban", "show"),
    ("kanban", "assign"),
    ("kanban", "reclaim"),
    ("kanban", "reassign"),
    ("kanban", "diagnose"),
    ("kanban", "link"),
    ("kanban", "unlink"),
    ("kanban", "claim"),
    ("kanban", "comment"),
    ("kanban", "complete"),
    ("kanban", "edit"),
    ("kanban", "block"),
    ("kanban", "schedule"),
    ("kanban", "unblock"),
    ("kanban", "promote"),
    ("kanban", "archive"),
    ("kanban", "stats"),
    ("kanban", "runs"),
    ("kanban", "heartbeat"),
    ("kanban", "assignments"),
    ("kanban", "context"),
    ("bundles", "list"),
    ("bundles", "show"),
    ("bundles", "create"),
    ("bundles", "delete"),
    ("bundles", "reload"),
    ("checkpoints", "status"),
    ("checkpoints", "list"),
    ("checkpoints", "prune"),
    ("checkpoints", "clear"),
    ("checkpoints", "clear-legacy"),
    ("curator", "status"),
    ("curator", "run"),
    ("curator", "pause"),
    ("curator", "resume"),
    ("curator", "pin"),
    ("curator", "unpin"),
    ("curator", "restore"),
    ("curator", "list-archived"),
    ("curator", "archive"),
    ("curator", "prune"),
    ("curator", "backup"),
    ("curator", "rollback"),
    ("pets", "list"),
    ("pets", "install"),
    ("pets", "select"),
    ("pets", "show"),
    ("pets", "off"),
    ("pets", "scale"),
    ("pets", "remove"),
    ("pets", "doctor"),
}


MUTATING_CONFIRMATION_SMOKE_COMMANDS = [
    "config set console.test true",
    "config migrate",
    "sessions rename abc123 new title",
    "sessions optimize",
    "cron create 'every 1h' 'say hello'",
    "cron remove abc123",
    "profile create tester --no-alias --no-skills",
    "profile delete tester",
    "tools disable web",
    "plugins install owner/repo --no-enable",
    "skills install openai/skills/example",
    "mcp add demo --url https://example.com/sse",
    "mcp configure github",
    "mcp picker",
    "backup --quick -o /tmp/hermes-console-test.zip",
    "import /tmp/hermes-console-test.zip",
    "send --to telegram hello",
    "memory reset --target memory",
    "auth remove openrouter 1",
    "pairing approve abc123",
    "webhook subscribe test --prompt hello",
    "hooks test pre_tool_call",
    "project create demo",
    "kanban create 'demo task'",
    "bundles create demo --skill skill-a",
    "checkpoints prune",
    "curator pause",
    "pets install cat",
]


def test_console_parses_bare_and_hermes_prefixed_commands(_isolate_hermes_home):
    engine = HermesConsoleEngine()

    bare = engine.execute("config path")
    prefixed = engine.execute("hermes config path")

    assert bare.status == "ok"
    assert prefixed.status == "ok"
    assert bare.output == prefixed.output
    assert bare.output.endswith("config.yaml")


def test_console_status_hides_cli_next_step_footer(
    monkeypatch: pytest.MonkeyPatch,
    _isolate_hermes_home,
):
    import hermes_cli.status as status_mod

    def fake_show_status(_args):
        print("◆ Sessions")
        print("Active: 3 session(s)")
        print()
        rule = "\u2500" * 60
        print(f"\x1b[2m{rule}\x1b[0m")
        print("\x1b[2m  Run 'hermes doctor' for detailed diagnostics\x1b[0m")
        print("\x1b[2m  Run 'hermes setup' to configure\x1b[0m")
        print()

    monkeypatch.setattr(status_mod, "show_status", fake_show_status)

    result = HermesConsoleEngine().execute("status")

    assert result.status == "ok"
    assert "Sessions" in result.output
    assert "Active: 3 session(s)" in result.output
    assert "hermes doctor" not in result.output
    assert "hermes setup" not in result.output
    assert "\u2500" not in result.output


def test_console_status_hides_osc_linked_cli_next_step_footer(
    monkeypatch: pytest.MonkeyPatch,
    _isolate_hermes_home,
):
    import hermes_cli.status as status_mod

    def osc_link(text: str) -> str:
        return f"\x1b]8;;https://example.test\x1b\\{text}\x1b]8;;\x1b\\"

    def fake_show_status(_args):
        print("◆ Sessions")
        print("Active: 3 session(s)")
        print()
        print(osc_link("\u2500" * 60))
        print(osc_link("  Run 'hermes doctor' for detailed diagnostics"))
        print(osc_link("  Run 'hermes setup' to configure"))
        print()

    monkeypatch.setattr(status_mod, "show_status", fake_show_status)

    result = HermesConsoleEngine().execute("status")

    assert result.status == "ok"
    assert "Sessions" in result.output
    assert "Active: 3 session(s)" in result.output
    assert "hermes doctor" not in result.output
    assert "hermes setup" not in result.output
    assert "https://example.test" not in result.output
    assert "\u2500" not in result.output


def test_console_help_uses_cli_subcommand_summaries():
    help_text = HermesConsoleEngine().help_text()

    assert "skills list" in help_text
    assert "List installed skills" in help_text
    assert "Show all tools and their enabled/disabled status" in help_text
    assert "Remove an MCP server" in help_text
    assert "Check pet setup + terminal graphics support" in help_text
    assert "Run `hermes skills list`" not in help_text
    assert "Run `hermes tools list`" not in help_text


def test_console_help_table_keeps_long_summaries_compact():
    help_text = HermesConsoleEngine().help_text()

    slack_line = next(
        line for line in help_text.splitlines() if line.strip().startswith("slack manifest")
    )

    assert len(slack_line) <= 112
    assert slack_line.endswith("...")


def test_console_help_for_command_uses_cli_summary():
    help_text = HermesConsoleEngine().help_text("skills list")

    assert help_text == "skills list\nList installed skills"


def test_console_registry_covers_non_admin_cli_surface():
    registered = set(HermesConsoleEngine().commands)

    missing = EXPECTED_CONSOLE_COMMANDS - registered

    assert missing == set()


EXPECTED_HOSTED_CONSOLE_COMMANDS = {
    ("status",),
    ("doctor",),
    ("logs",),
    ("version",),
    ("prompt-size",),
    ("insights",),
    ("security", "audit"),
    ("portal", "info"),
    ("portal", "tools"),
    ("send",),
    ("config", "show"),
    ("config", "path"),
    ("config", "env-path"),
    ("config", "check"),
    ("config", "migrate"),
    ("config", "set"),
    ("sessions", "list"),
    ("sessions", "stats"),
    ("sessions", "export"),
    ("sessions", "rename"),
    ("sessions", "optimize"),
    ("sessions", "repair"),
    ("cron", "list"),
    ("cron", "status"),
    ("cron", "create"),
    ("cron", "edit"),
    ("cron", "pause"),
    ("cron", "resume"),
    ("cron", "run"),
    ("cron", "remove"),
    ("cron", "tick"),
    ("profile",),
    ("profile", "list"),
    ("profile", "show"),
    ("profile", "info"),
    ("tools", "list"),
    ("tools", "enable"),
    ("tools", "disable"),
    ("tools", "post-setup"),
    ("skills", "browse"),
    ("skills", "search"),
    ("skills", "inspect"),
    ("skills", "list"),
    ("skills", "check"),
    ("skills", "list-modified"),
    ("skills", "diff"),
    ("skills", "install"),
    ("skills", "update"),
    ("skills", "audit"),
    ("skills", "uninstall"),
    ("skills", "reset"),
    ("skills", "opt-in"),
    ("skills", "opt-out"),
    ("skills", "repair-official"),
    ("skills", "snapshot", "export"),
    ("skills", "tap", "list"),
    ("mcp", "list"),
    ("mcp", "catalog"),
    ("mcp", "test"),
    ("mcp", "add"),
    ("mcp", "remove"),
    ("mcp", "install"),
    ("mcp", "login"),
    ("mcp", "reauth"),
    ("mcp", "configure"),
    ("mcp", "picker"),
    ("memory", "status"),
    ("auth", "list"),
    ("auth", "status"),
    ("auth", "reset"),
    ("auth", "spotify", "status"),
    ("pairing", "list"),
    ("pairing", "approve"),
    ("pairing", "revoke"),
    ("pairing", "clear-pending"),
    ("webhook", "list"),
    ("webhook", "subscribe"),
    ("webhook", "remove"),
    ("webhook", "test"),
}


def test_hosted_console_registry_exposes_only_hosted_safe_surface():
    engine = HermesConsoleEngine(context="hosted")
    hosted = {
        path for path, command in engine.commands.items() if "hosted" in command.contexts
    }

    assert hosted == EXPECTED_HOSTED_CONSOLE_COMMANDS


@pytest.mark.parametrize(
    "line",
    [
        "portal login",
        "auth add nous --type oauth",
        "auth logout nous",
        "profile create tester",
        "profile use default",
        "plugins list",
        "plugins install owner/repo",
        "kanban list",
        "hooks list",
        "checkpoints clear",
        "curator pause",
        "pets install cat",
        "backup --quick",
        "import /tmp/hermes-console-test.zip",
        "mcp serve",
        "model",
        "setup",
        "dashboard",
        "gateway restart",
        "update",
        "uninstall",
    ],
)
def test_hosted_console_rejects_local_only_or_dangerous_commands(line):
    result = HermesConsoleEngine(context="hosted").execute(line)

    assert result.status == "error"
    assert result.output


@pytest.mark.parametrize(
    "line",
    [
        "mcp add demo --url https://example.com/sse",
        "mcp install n8n",
        "mcp configure github",
        "mcp picker",
        "config set display.interface cli",
        "cron create 'every 1h' 'say hello'",
    ],
)
def test_hosted_console_allows_guarded_useful_commands_before_confirmation(line):
    result = HermesConsoleEngine(context="hosted").execute(line)

    assert result.status == "confirm_required"


@pytest.mark.parametrize(
    "line",
    [
        "mcp add local --command npx --args foo",
        "mcp add local --preset unsafe",
        "mcp add local --url file:///tmp/server",
        "config set model.provider openrouter",
        "config set portal.url https://evil.example",
        "cron create 'every 1h' 'say hello' --script scripts/ping.py",
        "cron create 'every 1h' 'say hello' --no-agent",
        "cron edit abc123 --workdir /tmp/project",
    ],
)
def test_hosted_console_blocks_known_footgun_arguments_before_confirmation(line):
    result = HermesConsoleEngine(context="hosted").execute(line)

    assert result.status == "error"
    assert result.output


@pytest.mark.parametrize(
    "line",
    [
        "sessions delete abc123",
        "sessions prune --older-than 1",
        "chat",
        "--cli",
        "--tui",
        "oneshot hello",
        "model",
        "setup",
        "postinstall",
        "fallback add",
        "moa configure",
        "claw migrate",
        "gateway restart",
        "gateway start",
        "gateway stop",
        "dashboard",
        "serve",
        "proxy start",
        "mcp serve",
        "skills config",
        "skills publish ./skill",
        "completion bash",
        "acp",
        "update",
        "uninstall",
        "gui",
        "desktop",
        "login",
        "logout",
        "--tui",
        "logs | cat",
        "config show > out.txt",
    ],
)
def test_console_rejects_destructive_and_shell_like_commands(line):
    result = HermesConsoleEngine().execute(line)

    assert result.status == "error"
    assert result.output


@pytest.mark.parametrize("line", MUTATING_CONFIRMATION_SMOKE_COMMANDS)
def test_mutating_console_commands_require_confirmation(line):
    result = HermesConsoleEngine().execute(line)

    assert result.status == "confirm_required"
    assert result.confirmation_message


def test_help_lists_supported_commands_and_not_full_cli():
    result = HermesConsoleEngine().execute("help")

    assert result.status == "ok"
    assert "sessions list" in result.output
    assert "config set" in result.output
    assert "dashboard" not in result.output
    assert "gateway restart" not in result.output


def test_config_set_requires_confirmation_then_writes(_isolate_hermes_home):
    engine = HermesConsoleEngine()

    pending = engine.execute("config set console.test true")
    assert pending.status == "confirm_required"

    from hermes_cli.config import read_raw_config

    assert read_raw_config() == {}

    result = engine.execute("config set console.test true", confirmed=True)

    assert result.status == "ok"
    assert "console.test" in result.output
    assert read_raw_config()["console"]["test"] is True


def test_sessions_list_and_stats_use_isolated_session_store(_isolate_hermes_home):
    from hermes_state import SessionDB

    db = SessionDB()
    try:
        db.create_session("chat-session", source="cli", model="test/model")
        db.create_session("tool-session", source="tool", model="test/model")
    finally:
        db.close()

    engine = HermesConsoleEngine()
    listed = engine.execute("sessions list --limit 10")
    stats = engine.execute("sessions stats")

    assert listed.status == "ok"
    assert "chat-session" in listed.output
    assert "tool-session" not in listed.output
    assert "Total sessions: 2" in stats.output
    assert "Listable sessions: 1" in stats.output


def test_cron_pause_resume_and_run_require_confirmation(_isolate_hermes_home):
    from cron.jobs import create_job, get_job

    job = create_job(prompt="say hello", schedule="every 1h", name="alpha")
    engine = HermesConsoleEngine()

    pending = engine.execute(f"cron pause {job['id']}")
    assert pending.status == "confirm_required"
    stored = get_job(job["id"])
    assert stored is not None
    assert stored["state"] == "scheduled"

    paused = engine.execute(f"cron pause {job['id']}", confirmed=True)
    assert paused.status == "ok"
    stored = get_job(job["id"])
    assert stored is not None
    assert stored["state"] == "paused"

    resumed = engine.execute("cron resume alpha", confirmed=True)
    assert resumed.status == "ok"
    stored = get_job(job["id"])
    assert stored is not None
    assert stored["state"] == "scheduled"

    triggered = engine.execute("cron run alpha", confirmed=True)
    assert triggered.status == "ok"
    assert "Triggered job" in triggered.output


def test_repl_runs_non_interactive_lines_without_prompts(_isolate_hermes_home):
    stdin = io.StringIO("help\nexit\n")
    stdout = io.StringIO()
    stderr = io.StringIO()

    code = run_console_repl(
        stdin=stdin,
        stdout=stdout,
        stderr=stderr,
        interactive=False,
    )

    assert code == 0
    assert "Hermes Console" in stdout.getvalue()
    assert "hermes>" not in stdout.getvalue()
    assert stderr.getvalue() == ""


def test_repl_refuses_non_interactive_confirmation(_isolate_hermes_home):
    stdin = io.StringIO("config set console.test true\n")
    stdout = io.StringIO()
    stderr = io.StringIO()

    code = run_console_repl(
        stdin=stdin,
        stdout=stdout,
        stderr=stderr,
        interactive=False,
    )

    assert code == 1
    assert "Confirmation required" in stderr.getvalue()


def test_main_console_subcommand_smoke(_isolate_hermes_home):
    import subprocess

    result = subprocess.run(
        [sys.executable, "-m", "hermes_cli.main", "console"],
        cwd=Path(__file__).resolve().parents[2],
        input="help\nexit\n",
        text=True,
        capture_output=True,
        timeout=20,
        check=False,
    )

    assert result.returncode == 0
    assert "Hermes Console" in result.stdout
