"""Supervisor routing of observer-emitted node liveness events → alert_only."""
from __future__ import annotations

import json
import sys
import time
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
import supervisor as supervisor_module
from supervisor import Supervisor


def _setup_supervisor(tmp_path, monkeypatch):
    actions = tmp_path / "actions"
    events = tmp_path / "events"
    world = tmp_path / "world"
    repo = tmp_path / "repo"
    state = tmp_path / "state"
    for d in (actions, events, world, repo / "hosts", state):
        d.mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr(supervisor_module, "ACTIONS_DIR", actions)
    monkeypatch.setattr(supervisor_module, "EVENTS_DIR", events)
    monkeypatch.setattr(supervisor_module, "WORLD_DIR", world)
    monkeypatch.setattr(supervisor_module, "REPO_ROOT", repo)
    sup = Supervisor()
    sup.desired_state = {"services": {}}
    sup.actual_state = {"services": {}, "nodes": {}, "incidents": {}}
    return sup


def _node_event(node, etype):
    return {
        "id": f"evt-{node}-{int(time.time())}-{etype}-node",
        "type": etype,
        "node": node,
        "service": None,
        "severity": "high",
        "timestamp": int(time.time()),
        "source": "observer",
        "message": f"Node {node} {etype}",
        "payload": {"affected_node": node, "from": "fresh", "to": "dead"},
    }


def _write(events_dir, event):
    p = events_dir / f"{event['id']}.json"
    p.write_text(json.dumps(event))
    return p


def _pending(tmp_path, action_id):
    return tmp_path / "actions" / "pending" / f"{action_id}.json"


def test_node_offline_generates_alert_only(tmp_path, monkeypatch):
    sup = _setup_supervisor(tmp_path, monkeypatch)
    _write(tmp_path / "events", _node_event("chelsty-infra", "node_offline"))

    sup._process_ha_events()

    action_id = "alert-node-offline-chelsty-infra"
    assert _pending(tmp_path, action_id).exists()
    action = json.loads(_pending(tmp_path, action_id).read_text())
    assert action["type"] == "alert_only"
    assert action["node"] == "chelsty-infra"
    assert action["payload"]["reason"] == "node_offline"


def test_node_stale_generates_alert_only(tmp_path, monkeypatch):
    sup = _setup_supervisor(tmp_path, monkeypatch)
    _write(tmp_path / "events", _node_event("piha", "node_stale"))

    sup._process_ha_events()

    assert _pending(tmp_path, "alert-node-stale-piha").exists()


def test_node_online_recovery_generates_alert_only(tmp_path, monkeypatch):
    sup = _setup_supervisor(tmp_path, monkeypatch)
    _write(tmp_path / "events", _node_event("piha", "node_online"))

    sup._process_ha_events()

    assert _pending(tmp_path, "alert-node-online-piha").exists()


def test_node_alert_deduped_within_cooldown(tmp_path, monkeypatch):
    """A second identical event must not create a second pending action."""
    sup = _setup_supervisor(tmp_path, monkeypatch)
    _write(tmp_path / "events", _node_event("chelsty-infra", "node_offline"))
    sup._process_ha_events()

    # Reset the in-memory processed-id set so the file is re-read, but the
    # action already exists in pending → must be skipped.
    sup._ha_processed_event_ids.clear()
    sup._process_ha_events()

    pending = list((tmp_path / "actions" / "pending").glob("*.json"))
    assert len(pending) == 1


def test_node_alert_uses_affected_node_when_top_level_missing(tmp_path, monkeypatch):
    """If node is absent at top level, fall back to payload.affected_node."""
    sup = _setup_supervisor(tmp_path, monkeypatch)
    ev = _node_event("solaria", "node_offline")
    ev["node"] = None
    _write(tmp_path / "events", ev)

    sup._process_ha_events()

    assert _pending(tmp_path, "alert-node-offline-solaria").exists()


def test_non_node_non_ha_event_not_routed(tmp_path, monkeypatch):
    sup = _setup_supervisor(tmp_path, monkeypatch)
    ev = _node_event("vps", "node_health")  # not in NODE_ALERT_EVENTS
    _write(tmp_path / "events", ev)

    sup._process_ha_events()

    assert list((tmp_path / "actions" / "pending").glob("*.json")) == []
