"""Tests for brynq.swarm_poke — Automated Poke Mechanism (Phase 70)."""

import json
import pytest
from datetime import datetime, timezone, timedelta
from pathlib import Path


@pytest.fixture(autouse=True)
def _isolate_poke(tmp_path, monkeypatch):
    """Redirect poke storage to temp directory."""
    poke_dir = tmp_path / "poke"
    poke_dir.mkdir()
    config_dir = poke_dir / "config"
    config_dir.mkdir()
    channels_dir = tmp_path / "channels"
    channels_dir.mkdir()
    tasks_dir = tmp_path / "tasks"
    tasks_dir.mkdir()
    monkeypatch.setattr("brynq.swarm_poke.POKE_DIR", poke_dir)
    monkeypatch.setattr("brynq.swarm_poke.ACTIVITY_FILE", poke_dir / "activity.jsonl")
    monkeypatch.setattr("brynq.swarm_poke.HISTORY_FILE", poke_dir / "history.jsonl")
    monkeypatch.setattr("brynq.swarm_poke.CONFIG_DIR", config_dir)
    monkeypatch.setattr("brynq.swarm_poke.BROKER_DIR", tmp_path)


# ── register_agent_activity ───────────────────────────────────────────


class TestRegisterActivity:
    def test_returns_record(self):
        from brynq.swarm_poke import register_agent_activity
        record = register_agent_activity("agent-1")
        assert record["agent_id"] == "agent-1"
        assert record["activity_type"] == "heartbeat"
        assert "timestamp" in record

    def test_custom_activity_type(self):
        from brynq.swarm_poke import register_agent_activity
        record = register_agent_activity("agent-1", activity_type="message")
        assert record["activity_type"] == "message"

    def test_writes_to_file(self):
        from brynq.swarm_poke import register_agent_activity, ACTIVITY_FILE
        register_agent_activity("agent-1")
        assert ACTIVITY_FILE.exists()
        content = ACTIVITY_FILE.read_text("utf-8").strip()
        assert "agent-1" in content

    def test_multiple_activities(self):
        from brynq.swarm_poke import register_agent_activity, ACTIVITY_FILE
        from brynq.server import _read_jsonl_sync
        register_agent_activity("agent-1")
        register_agent_activity("agent-2")
        register_agent_activity("agent-1", activity_type="task_update")
        entries = _read_jsonl_sync(ACTIVITY_FILE)
        assert len(entries) == 3


# ── get_agent_last_activity ───────────────────────────────────────────


class TestGetAgentLastActivity:
    def test_no_activity(self):
        from brynq.swarm_poke import get_agent_last_activity
        assert get_agent_last_activity("agent-x") is None

    def test_returns_latest(self):
        from brynq.swarm_poke import register_agent_activity, get_agent_last_activity
        register_agent_activity("agent-1", activity_type="heartbeat")
        register_agent_activity("agent-1", activity_type="message")
        last = get_agent_last_activity("agent-1")
        assert last["activity_type"] == "message"

    def test_returns_correct_agent(self):
        from brynq.swarm_poke import register_agent_activity, get_agent_last_activity
        register_agent_activity("agent-1")
        register_agent_activity("agent-2")
        last = get_agent_last_activity("agent-1")
        assert last["agent_id"] == "agent-1"


# ── get_idle_agents ───────────────────────────────────────────────────


class TestGetIdleAgents:
    def test_no_activity_returns_empty(self):
        from brynq.swarm_poke import get_idle_agents
        assert get_idle_agents() == []

    def test_recent_agents_not_idle(self):
        from brynq.swarm_poke import register_agent_activity, get_idle_agents
        register_agent_activity("agent-1")
        idle = get_idle_agents(threshold_minutes=15)
        assert len(idle) == 0

    def test_old_activity_is_idle(self, monkeypatch):
        from brynq.swarm_poke import register_agent_activity, get_idle_agents, ACTIVITY_FILE
        from brynq.server import _append_jsonl_sync
        # Write an old activity record directly
        old_time = (datetime.now(timezone.utc) - timedelta(minutes=30)).isoformat()
        _append_jsonl_sync(ACTIVITY_FILE, {
            "agent_id": "old-agent",
            "activity_type": "heartbeat",
            "timestamp": old_time,
        })
        idle = get_idle_agents(threshold_minutes=15)
        assert len(idle) == 1
        assert idle[0]["agent_id"] == "old-agent"
        assert idle[0]["idle_minutes"] >= 15

    def test_custom_threshold(self):
        from brynq.swarm_poke import register_agent_activity, get_idle_agents, ACTIVITY_FILE
        from brynq.server import _append_jsonl_sync
        old_time = (datetime.now(timezone.utc) - timedelta(minutes=10)).isoformat()
        _append_jsonl_sync(ACTIVITY_FILE, {
            "agent_id": "semi-idle",
            "activity_type": "heartbeat",
            "timestamp": old_time,
        })
        # 15 min threshold: not idle
        assert len(get_idle_agents(threshold_minutes=15)) == 0
        # 5 min threshold: idle
        assert len(get_idle_agents(threshold_minutes=5)) == 1


# ── poke_agent ────────────────────────────────────────────────────────


class TestPokeAgent:
    def test_basic_poke(self):
        from brynq.swarm_poke import poke_agent
        result = poke_agent("agent-1", "Wake up!")
        assert result["poked"] is True
        assert result["agent_id"] == "agent-1"

    def test_poke_posts_to_channel(self, tmp_path):
        from brynq.swarm_poke import poke_agent, BROKER_DIR
        from brynq.server import _read_jsonl_sync
        poke_agent("agent-1", "Any blockers?", channel="dev")
        ch_file = BROKER_DIR / "channels" / "dev.jsonl"
        msgs = _read_jsonl_sync(ch_file)
        assert len(msgs) == 1
        assert "@agent-1" in msgs[0]["message"]

    def test_poke_records_history(self):
        from brynq.swarm_poke import poke_agent, HISTORY_FILE
        from brynq.server import _read_jsonl_sync
        poke_agent("agent-1", "Nudge")
        entries = _read_jsonl_sync(HISTORY_FILE)
        assert len(entries) == 1
        assert entries[0]["poked"] is True

    def test_opted_out_agent_not_poked(self):
        from brynq.swarm_poke import poke_agent, set_poke_config
        set_poke_config("agent-opt", {"opt_out": True})
        result = poke_agent("agent-opt", "Hey!")
        assert result["poked"] is False
        assert result["reason"] == "opted_out"

    def test_rate_limited(self):
        from brynq.swarm_poke import poke_agent, set_poke_config
        set_poke_config("agent-rl", {"max_pokes_per_hour": 2, "min_interval_minutes": 0})
        poke_agent("agent-rl", "Poke 1")
        poke_agent("agent-rl", "Poke 2")
        result = poke_agent("agent-rl", "Poke 3")
        assert result["poked"] is False
        assert result["reason"] == "rate_limited"

    def test_custom_channel(self):
        from brynq.swarm_poke import poke_agent, BROKER_DIR
        from brynq.server import _read_jsonl_sync
        poke_agent("agent-1", "Check this", channel="alerts")
        ch_file = BROKER_DIR / "channels" / "alerts.jsonl"
        msgs = _read_jsonl_sync(ch_file)
        assert len(msgs) == 1


# ── auto_poke_idle ────────────────────────────────────────────────────


class TestAutoPokeIdle:
    def test_no_idle_agents(self):
        from brynq.swarm_poke import auto_poke_idle
        results = auto_poke_idle()
        assert results == []

    def test_pokes_idle_agents(self):
        from brynq.swarm_poke import auto_poke_idle, ACTIVITY_FILE
        from brynq.server import _append_jsonl_sync
        old_time = (datetime.now(timezone.utc) - timedelta(minutes=30)).isoformat()
        _append_jsonl_sync(ACTIVITY_FILE, {
            "agent_id": "lazy-1",
            "activity_type": "heartbeat",
            "timestamp": old_time,
        })
        _append_jsonl_sync(ACTIVITY_FILE, {
            "agent_id": "lazy-2",
            "activity_type": "heartbeat",
            "timestamp": old_time,
        })
        results = auto_poke_idle(threshold_minutes=15)
        assert len(results) == 2
        assert all(r["poked"] for r in results)


# ── set_poke_config / get_poke_history ────────────────────────────────


class TestSetPokeConfig:
    def test_set_config(self):
        from brynq.swarm_poke import set_poke_config
        config = set_poke_config("agent-c", {"min_interval_minutes": 10, "opt_out": False})
        assert config["agent_id"] == "agent-c"
        assert config["min_interval_minutes"] == 10

    def test_config_persists(self):
        from brynq.swarm_poke import set_poke_config, CONFIG_DIR
        set_poke_config("agent-p", {"max_pokes_per_hour": 6})
        path = CONFIG_DIR / "agent-p.json"
        assert path.exists()
        data = json.loads(path.read_text("utf-8"))
        assert data["max_pokes_per_hour"] == 6


class TestGetPokeHistory:
    def test_empty_history(self):
        from brynq.swarm_poke import get_poke_history
        assert get_poke_history() == []

    def test_returns_pokes(self):
        from brynq.swarm_poke import poke_agent, get_poke_history
        poke_agent("agent-h1", "First poke")
        poke_agent("agent-h2", "Second poke")
        history = get_poke_history()
        assert len(history) == 2

    def test_filter_by_agent(self):
        from brynq.swarm_poke import poke_agent, get_poke_history
        poke_agent("agent-f1", "Poke A")
        poke_agent("agent-f2", "Poke B")
        history = get_poke_history(agent_id="agent-f1")
        assert len(history) == 1
        assert history[0]["agent_id"] == "agent-f1"

    def test_limit(self):
        from brynq.swarm_poke import poke_agent, get_poke_history, set_poke_config
        set_poke_config("agent-lim", {"max_pokes_per_hour": 100, "min_interval_minutes": 0})
        for i in range(10):
            poke_agent("agent-lim", f"Poke {i}")
        history = get_poke_history(limit=3)
        assert len(history) == 3


# ── check_dependency_ready ────────────────────────────────────────────


class TestCheckDependencyReady:
    def test_no_tasks_returns_none(self):
        from brynq.swarm_poke import check_dependency_ready
        assert check_dependency_ready("agent-x") is None

    def test_blocked_with_resolved_deps(self, tmp_path):
        from brynq.swarm_poke import check_dependency_ready, BROKER_DIR
        tasks_dir = BROKER_DIR / "tasks"
        tasks_dir.mkdir(parents=True, exist_ok=True)
        # Create dependency task (completed)
        dep = {"task_id": "dep-1", "status": "completed", "title": "Dependency"}
        (tasks_dir / "dep-1.json").write_text(json.dumps(dep))
        # Create blocked task
        blocked = {
            "task_id": "blocked-1",
            "status": "blocked",
            "assigned_to": "agent-b",
            "depends_on": ["dep-1"],
            "title": "Blocked Task",
        }
        (tasks_dir / "blocked-1.json").write_text(json.dumps(blocked))
        result = check_dependency_ready("agent-b")
        assert result is not None
        assert result["poked"] is True

    def test_blocked_with_unresolved_deps(self, tmp_path):
        from brynq.swarm_poke import check_dependency_ready, BROKER_DIR
        tasks_dir = BROKER_DIR / "tasks"
        tasks_dir.mkdir(parents=True, exist_ok=True)
        dep = {"task_id": "dep-2", "status": "in_progress", "title": "Dep"}
        (tasks_dir / "dep-2.json").write_text(json.dumps(dep))
        blocked = {
            "task_id": "blocked-2",
            "status": "blocked",
            "assigned_to": "agent-u",
            "depends_on": ["dep-2"],
            "title": "Still Blocked",
        }
        (tasks_dir / "blocked-2.json").write_text(json.dumps(blocked))
        result = check_dependency_ready("agent-u")
        assert result is None
