"""Regression tests for #33488 (CLI max_in_progress / max_spawn / per-profile
config passthrough) and #29415 (kanban_swarm humanizer skill ref).

These two fixes are bundled because they're both small, both touch the
kanban dispatcher's CLI surface, and they each guard against a silent
operator footgun that only manifests in long-running setups.
"""
from __future__ import annotations

import argparse
import os
import sys
import tempfile
from types import SimpleNamespace
from unittest.mock import MagicMock, patch

import pytest


@pytest.fixture()
def isolated_kanban_home(monkeypatch):
    """Spin up a fresh HERMES_HOME with a clean kanban DB."""
    test_home = tempfile.mkdtemp(prefix="kanban_cli_passthrough_")
    os.makedirs(os.path.join(test_home, "profiles", "default"), exist_ok=True)
    monkeypatch.setenv("HERMES_HOME", test_home)
    for mod in list(sys.modules.keys()):
        if mod.startswith("hermes_cli") or mod.startswith("hermes_state") or mod == "hermes_constants":
            del sys.modules[mod]
    yield test_home


def test_cli_dispatch_passes_max_in_progress_from_config(isolated_kanban_home, monkeypatch):
    """#33488: hermes kanban dispatch must pass kanban.max_in_progress from
    config to dispatch_once. Without this, the global concurrency cap is
    unreachable from the CLI even though it works from the gateway."""
    from hermes_cli import kanban as kb_cli
    from hermes_cli import kanban_db

    # Configure max_in_progress in the loaded config.
    fake_config = {
        "kanban": {
            "max_in_progress": 3,
            "max_spawn": 5,
            "default_assignee": "default",
            "max_in_progress_per_profile": 2,
        }
    }
    monkeypatch.setattr(
        "hermes_cli.config.load_config", lambda: fake_config
    )

    captured = {}

    def fake_dispatch_once(conn, **kwargs):
        captured.update(kwargs)
        return kanban_db.DispatchResult()

    monkeypatch.setattr(kanban_db, "dispatch_once", fake_dispatch_once)

    args = argparse.Namespace(dry_run=True, max=None, failure_limit=2, json=False)
    kb_cli._cmd_dispatch(args)

    # Every config value must have reached dispatch_once.
    assert captured.get("max_in_progress") == 3, (
        f"CLI must pass kanban.max_in_progress from config; got {captured.get('max_in_progress')!r}"
    )
    assert captured.get("max_spawn") == 5, (
        f"CLI must pass kanban.max_spawn from config when --max is not provided; got {captured.get('max_spawn')!r}"
    )
    assert captured.get("default_assignee") == "default"
    assert captured.get("max_in_progress_per_profile") == 2


def test_cli_max_flag_overrides_config_max_spawn(isolated_kanban_home, monkeypatch):
    """--max on the CLI takes precedence over kanban.max_spawn in config.
    The CLI flag is the explicit operator signal; config is the default."""
    from hermes_cli import kanban as kb_cli
    from hermes_cli import kanban_db

    fake_config = {"kanban": {"max_spawn": 10}}
    monkeypatch.setattr("hermes_cli.config.load_config", lambda: fake_config)

    captured = {}
    monkeypatch.setattr(
        kanban_db, "dispatch_once",
        lambda conn, **kw: (captured.update(kw), kanban_db.DispatchResult())[1],
    )

    args = argparse.Namespace(dry_run=True, max=2, failure_limit=2, json=False)
    kb_cli._cmd_dispatch(args)

    assert captured.get("max_spawn") == 2, (
        f"CLI --max=2 must override config kanban.max_spawn=10; got {captured.get('max_spawn')!r}"
    )


def test_cli_invalid_max_in_progress_silently_disables(isolated_kanban_home, monkeypatch):
    """Invalid kanban.max_in_progress values (0, negative, non-int) should
    silently fall through to None — no crash, no surprise behavior."""
    from hermes_cli import kanban as kb_cli
    from hermes_cli import kanban_db

    for bad_val in (0, -1, "abc", "1.5"):
        fake_config = {"kanban": {"max_in_progress": bad_val}}
        monkeypatch.setattr("hermes_cli.config.load_config", lambda: fake_config)
        captured = {}
        monkeypatch.setattr(
            kanban_db, "dispatch_once",
            lambda conn, **kw: (captured.update(kw), kanban_db.DispatchResult())[1],
        )
        args = argparse.Namespace(dry_run=True, max=None, failure_limit=2, json=False)
        kb_cli._cmd_dispatch(args)
        assert captured.get("max_in_progress") is None, (
            f"invalid max_in_progress={bad_val!r} should fall through to None, "
            f"got {captured.get('max_in_progress')!r}"
        )


def test_kanban_swarm_uses_existing_humanizer_skill():
    """#29415: kanban_swarm.py used to hardcode skills=['avoid-ai-writing'],
    a skill that doesn't exist in any registry — synthesizer workers
    crashed with 'Unknown skill(s): avoid-ai-writing' on every retry.

    Verify the synthesizer card now uses the bundled 'humanizer' skill
    which actually exists at skills/creative/humanizer/SKILL.md."""
    import pathlib

    swarm_path = (
        pathlib.Path(__file__).resolve().parent.parent.parent
        / "hermes_cli" / "kanban_swarm.py"
    )
    src = swarm_path.read_text()
    assert "avoid-ai-writing" not in src, (
        "kanban_swarm.py must not reference 'avoid-ai-writing' — that "
        "skill doesn't exist in any registry, crashing synthesizers (#29415)"
    )
    assert '"humanizer"' in src, (
        "kanban_swarm.py should use the bundled 'humanizer' skill for "
        "synthesizer cards (the original intent of 'avoid-ai-writing')"
    )

    # And the replacement skill must actually exist on disk.
    skills_root = (
        pathlib.Path(__file__).resolve().parent.parent.parent / "skills"
    )
    humanizer_path = skills_root / "creative" / "humanizer" / "SKILL.md"
    assert humanizer_path.is_file(), (
        f"humanizer skill missing at {humanizer_path}; the kanban_swarm fix "
        "for #29415 requires this bundled skill to exist"
    )
