"""
Tests for alert_store + alert_ingest logic.
Covers: ingest with dedupe, list/get/ack, RBAC entitlements, severity validation.
"""
import os
import sys
import time
from datetime import datetime, timedelta
from pathlib import Path
from unittest.mock import patch

ROOT = Path(__file__).resolve().parent.parent
ROUTER = ROOT / "services" / "router"
if str(ROUTER) not in sys.path:
    sys.path.insert(0, str(ROUTER))


def _make_alert(service="gateway", severity="P1", kind="slo_breach",
                fingerprint="abc123", title="High latency"):
    return {
        "source": "monitor@node1",
        "service": service,
        "env": "prod",
        "severity": severity,
        "kind": kind,
        "title": title,
        "summary": f"{service} is experiencing {kind}",
        "started_at": datetime.utcnow().isoformat(),
        "labels": {"node": "node1", "fingerprint": fingerprint},
        "metrics": {"latency_p95_ms": 450, "error_rate_pct": 2.5},
        "evidence": {
            "log_samples": ["ERROR timeout after 30s", "WARN retry 3/3"],
        },
    }


class TestMemoryAlertStoreIngest:
    def setup_method(self):
        from alert_store import MemoryAlertStore, set_alert_store
        self.store = MemoryAlertStore()
        set_alert_store(self.store)

    def teardown_method(self):
        from alert_store import set_alert_store
        set_alert_store(None)

    def test_ingest_new_alert(self):
        from alert_ingest import ingest_alert
        result = ingest_alert(self.store, _make_alert())
        assert result["accepted"] is True
        assert result["deduped"] is False
        assert result["occurrences"] == 1
        assert result["alert_ref"].startswith("alrt_")
        assert len(result["dedupe_key"]) == 32

    def test_ingest_duplicate_within_ttl(self):
        from alert_ingest import ingest_alert
        alert = _make_alert(fingerprint="dup_key")
        r1 = ingest_alert(self.store, alert, dedupe_ttl_minutes=30)
        r2 = ingest_alert(self.store, alert, dedupe_ttl_minutes=30)
        assert r2["deduped"] is True
        assert r2["occurrences"] == 2
        assert r2["alert_ref"] == r1["alert_ref"]

    def test_ingest_after_ttl_creates_new(self):
        from alert_ingest import ingest_alert
        from alert_store import MemoryAlertStore
        alert = _make_alert(fingerprint="expire_test")
        store2 = MemoryAlertStore()
        r1 = ingest_alert(store2, alert, dedupe_ttl_minutes=30)

        # Manipulate created_at to be older than TTL
        with store2._lock:
            ref = r1["alert_ref"]
            store2._alerts[ref]["created_at"] = (
                datetime.utcnow() - timedelta(minutes=60)
            ).isoformat()

        r2 = ingest_alert(store2, alert, dedupe_ttl_minutes=30)
        assert r2["deduped"] is False
        # New ref or same ref (depending on whether store evicts) — occurrences reset
        assert r2["occurrences"] == 1

    def test_different_fingerprint_creates_separate(self):
        from alert_ingest import ingest_alert
        r1 = ingest_alert(self.store, _make_alert(fingerprint="a"))
        r2 = ingest_alert(self.store, _make_alert(fingerprint="b"))
        assert r1["alert_ref"] != r2["alert_ref"]
        assert r1["dedupe_key"] != r2["dedupe_key"]

    def test_list_alerts(self):
        from alert_ingest import ingest_alert, list_alerts
        ingest_alert(self.store, _make_alert(service="gateway"))
        ingest_alert(self.store, _make_alert(service="router"))
        all_alerts = list_alerts(self.store)
        assert len(all_alerts) >= 2

        gw_alerts = list_alerts(self.store, service="gateway")
        assert all(a["service"] == "gateway" for a in gw_alerts)

    def test_get_alert(self):
        from alert_ingest import ingest_alert, get_alert
        r = ingest_alert(self.store, _make_alert())
        fetched = get_alert(self.store, r["alert_ref"])
        assert fetched is not None
        assert fetched["alert_ref"] == r["alert_ref"]
        assert fetched["service"] == "gateway"
        assert "evidence" in fetched

    def test_get_nonexistent(self):
        from alert_ingest import get_alert
        assert get_alert(self.store, "nonexistent") is None

    def test_ack_alert(self):
        from alert_ingest import ingest_alert, ack_alert, get_alert
        r = ingest_alert(self.store, _make_alert())
        ack_result = ack_alert(self.store, r["alert_ref"], "sofiia", note="handled")
        assert ack_result["ack_status"] == "acked"
        fetched = get_alert(self.store, r["alert_ref"])
        assert fetched["ack_status"] == "acked"
        assert fetched["ack_actor"] == "sofiia"

    def test_ack_nonexistent(self):
        from alert_ingest import ack_alert
        result = ack_alert(self.store, "nonexistent", "sofiia")
        assert result is None


class TestAlertValidation:
    def setup_method(self):
        from alert_store import MemoryAlertStore, set_alert_store
        self.store = MemoryAlertStore()
        set_alert_store(self.store)

    def teardown_method(self):
        from alert_store import set_alert_store
        set_alert_store(None)

    def test_missing_service_rejected(self):
        from alert_ingest import ingest_alert
        alert = _make_alert()
        del alert["service"]
        result = ingest_alert(self.store, alert)
        assert result["accepted"] is False
        assert "service" in result["error"]

    def test_missing_title_rejected(self):
        from alert_ingest import ingest_alert
        alert = _make_alert()
        del alert["title"]
        result = ingest_alert(self.store, alert)
        assert result["accepted"] is False

    def test_invalid_severity_rejected(self):
        from alert_ingest import ingest_alert
        alert = _make_alert()
        alert["severity"] = "CRITICAL"  # not in our enum
        result = ingest_alert(self.store, alert)
        assert result["accepted"] is False

    def test_invalid_kind_rejected(self):
        from alert_ingest import ingest_alert
        alert = _make_alert()
        alert["kind"] = "unknown_kind"
        result = ingest_alert(self.store, alert)
        assert result["accepted"] is False

    def test_secret_redacted_in_summary(self):
        from alert_ingest import ingest_alert, get_alert
        alert = _make_alert()
        alert["summary"] = "Error: token=sk-secret123 caused issue"
        r = ingest_alert(self.store, alert)
        fetched = get_alert(self.store, r["alert_ref"])
        assert "sk-secret123" not in fetched["summary"]
        assert "***" in fetched["summary"]

    def test_evidence_truncated(self):
        from alert_ingest import ingest_alert, get_alert
        alert = _make_alert()
        alert["evidence"] = {"log_samples": [f"line {i}" for i in range(100)]}
        r = ingest_alert(self.store, alert)
        fetched = get_alert(self.store, r["alert_ref"])
        assert len(fetched["evidence"]["log_samples"]) <= 40


class TestAlertRBAC:
    """Verify RBAC entitlements for alert_ingest_tool actions."""

    def test_monitor_has_ingest_entitlement(self):
        import yaml
        rbac_path = ROOT / "config" / "rbac_tools_matrix.yml"
        with open(rbac_path) as f:
            matrix = yaml.safe_load(f)
        monitor_ents = set(matrix["role_entitlements"]["agent_monitor"])
        assert "tools.alerts.ingest" in monitor_ents

    def test_monitor_has_no_ack_entitlement(self):
        import yaml
        rbac_path = ROOT / "config" / "rbac_tools_matrix.yml"
        with open(rbac_path) as f:
            matrix = yaml.safe_load(f)
        monitor_ents = set(matrix["role_entitlements"]["agent_monitor"])
        assert "tools.alerts.ack" not in monitor_ents

    def test_cto_has_all_alert_entitlements(self):
        import yaml
        rbac_path = ROOT / "config" / "rbac_tools_matrix.yml"
        with open(rbac_path) as f:
            matrix = yaml.safe_load(f)
        cto_ents = set(matrix["role_entitlements"]["agent_cto"])
        for ent in ("tools.alerts.ingest", "tools.alerts.read", "tools.alerts.ack"):
            assert ent in cto_ents, f"Missing: {ent}"

    def test_interface_has_read_only(self):
        import yaml
        rbac_path = ROOT / "config" / "rbac_tools_matrix.yml"
        with open(rbac_path) as f:
            matrix = yaml.safe_load(f)
        iface_ents = set(matrix["role_entitlements"]["agent_interface"])
        assert "tools.alerts.read" in iface_ents
        assert "tools.alerts.ack" not in iface_ents
        assert "tools.alerts.ingest" not in iface_ents


class TestAlertStoreFactory:
    def test_default_is_memory(self):
        from alert_store import _create_alert_store, MemoryAlertStore
        env = {"ALERT_BACKEND": "memory"}
        with patch.dict(os.environ, env, clear=False):
            store = _create_alert_store()
        assert isinstance(store, MemoryAlertStore)

    def test_auto_with_dsn(self):
        from alert_store import _create_alert_store, AutoAlertStore
        env = {"ALERT_BACKEND": "auto", "DATABASE_URL": "postgresql://x:x@localhost/test"}
        with patch.dict(os.environ, env, clear=False):
            store = _create_alert_store()
        assert isinstance(store, AutoAlertStore)

    def test_auto_without_dsn_gives_memory(self):
        from alert_store import _create_alert_store, MemoryAlertStore
        env_clear = {k: v for k, v in os.environ.items()
                     if k not in ("DATABASE_URL", "ALERT_DATABASE_URL")}
        env_clear["ALERT_BACKEND"] = "auto"
        with patch.dict(os.environ, env_clear, clear=True):
            store = _create_alert_store()
        assert isinstance(store, MemoryAlertStore)
