"""Tests for Notification System."""

from __future__ import annotations

import json
from datetime import datetime, timedelta, timezone

import pytest

from mcpaudit.notification import (
    NotificationBatcher,
    NotificationConfig,
    NotificationEvent,
    NotificationFormatter,
    NotificationHistory,
    filter_events,
    is_quiet_hours,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _event(
    rule_id="CMD-001", severity="HIGH", title="Test",
    file="server.py", line=10,
) -> NotificationEvent:
    return NotificationEvent(
        event_id=f"evt-{rule_id}",
        rule_id=rule_id,
        severity=severity,
        title=title,
        file=file,
        line=line,
        description=f"Desc: {title}",
        timestamp="2025-01-01T00:00:00Z",
    )


# ---------------------------------------------------------------------------
# NotificationConfig
# ---------------------------------------------------------------------------


class TestNotificationConfig:
    def test_defaults(self):
        cfg = NotificationConfig()
        assert cfg.webhook_url == ""
        assert cfg.severity_threshold == "MEDIUM"
        assert cfg.channels == []
        assert cfg.quiet_hours_start == -1
        assert cfg.batch_window_seconds == 300
        assert cfg.enabled is True

    def test_to_dict(self):
        cfg = NotificationConfig(
            webhook_url="https://hook.example.com",
            severity_threshold="HIGH",
            channels=["security"],
        )
        d = cfg.to_dict()
        assert d["webhook_url"] == "https://hook.example.com"
        assert d["severity_threshold"] == "HIGH"
        assert d["channels"] == ["security"]

    def test_from_dict(self):
        data = {
            "webhook_url": "https://x.com",
            "severity_threshold": "CRITICAL",
            "batch_window_seconds": 60,
        }
        cfg = NotificationConfig.from_dict(data)
        assert cfg.webhook_url == "https://x.com"
        assert cfg.severity_threshold == "CRITICAL"
        assert cfg.batch_window_seconds == 60

    def test_from_dict_defaults(self):
        cfg = NotificationConfig.from_dict({})
        assert cfg.severity_threshold == "MEDIUM"
        assert cfg.enabled is True

    def test_validate_valid(self):
        cfg = NotificationConfig()
        assert cfg.validate() == []

    def test_validate_invalid_severity(self):
        cfg = NotificationConfig(severity_threshold="ULTRA")
        errors = cfg.validate()
        assert any("severity_threshold" in e for e in errors)

    def test_validate_invalid_quiet_hours(self):
        cfg = NotificationConfig(quiet_hours_start=25)
        errors = cfg.validate()
        assert any("quiet_hours_start" in e for e in errors)

    def test_validate_negative_batch_window(self):
        cfg = NotificationConfig(batch_window_seconds=-1)
        errors = cfg.validate()
        assert any("batch_window" in e for e in errors)


# ---------------------------------------------------------------------------
# NotificationEvent
# ---------------------------------------------------------------------------


class TestNotificationEvent:
    def test_defaults(self):
        e = NotificationEvent()
        assert e.severity == "INFO"
        assert e.timestamp != ""

    def test_custom_timestamp(self):
        e = NotificationEvent(timestamp="2025-06-01")
        assert e.timestamp == "2025-06-01"

    def test_fingerprint_deterministic(self):
        e = _event()
        fp1 = e.fingerprint()
        fp2 = e.fingerprint()
        assert fp1 == fp2
        assert len(fp1) == 16

    def test_fingerprint_differs(self):
        e1 = _event(rule_id="A")
        e2 = _event(rule_id="B")
        assert e1.fingerprint() != e2.fingerprint()

    def test_to_dict(self):
        e = _event()
        d = e.to_dict()
        assert d["rule_id"] == "CMD-001"
        assert d["severity"] == "HIGH"

    def test_from_dict(self):
        data = {"rule_id": "X", "severity": "LOW", "title": "T"}
        e = NotificationEvent.from_dict(data)
        assert e.rule_id == "X"
        assert e.severity == "LOW"

    def test_meets_threshold_yes(self):
        e = _event(severity="CRITICAL")
        assert e.meets_threshold("HIGH") is True

    def test_meets_threshold_equal(self):
        e = _event(severity="MEDIUM")
        assert e.meets_threshold("MEDIUM") is True

    def test_meets_threshold_no(self):
        e = _event(severity="LOW")
        assert e.meets_threshold("MEDIUM") is False

    def test_meets_threshold_info(self):
        e = _event(severity="INFO")
        assert e.meets_threshold("LOW") is False


# ---------------------------------------------------------------------------
# NotificationFormatter
# ---------------------------------------------------------------------------


class TestNotificationFormatter:
    def test_format_json(self):
        events = [_event()]
        result = NotificationFormatter.format_json(events)
        data = json.loads(result)
        assert data["count"] == 1
        assert data["notifications"][0]["rule_id"] == "CMD-001"

    def test_format_json_empty(self):
        result = NotificationFormatter.format_json([])
        data = json.loads(result)
        assert data["count"] == 0

    def test_format_markdown(self):
        events = [_event(title="Injection", severity="CRITICAL")]
        result = NotificationFormatter.format_markdown(events)
        assert "**[CRITICAL]**" in result
        assert "Injection" in result
        assert "CMD-001" in result

    def test_format_markdown_empty(self):
        result = NotificationFormatter.format_markdown([])
        assert "No notifications" in result

    def test_format_text(self):
        events = [_event()]
        result = NotificationFormatter.format_text(events)
        assert "[HIGH]" in result
        assert "Test" in result

    def test_format_text_empty(self):
        result = NotificationFormatter.format_text([])
        assert "No notifications" in result

    def test_format_dispatch_json(self):
        result = NotificationFormatter.format([_event()], "json")
        data = json.loads(result)
        assert "notifications" in data

    def test_format_dispatch_markdown(self):
        result = NotificationFormatter.format([_event()], "markdown")
        assert "##" in result

    def test_format_dispatch_text(self):
        result = NotificationFormatter.format([_event()], "text")
        assert "Security Notifications" in result

    def test_format_invalid(self):
        with pytest.raises(ValueError):
            NotificationFormatter.format([_event()], "xml")


# ---------------------------------------------------------------------------
# NotificationBatcher
# ---------------------------------------------------------------------------


class TestNotificationBatcher:
    def test_add_event(self):
        batcher = NotificationBatcher(window_seconds=60)
        batcher.add(_event())
        assert batcher.pending_count == 1

    def test_add_below_threshold(self):
        batcher = NotificationBatcher(
            window_seconds=60, severity_threshold="HIGH",
        )
        batcher.add(_event(severity="LOW"))
        assert batcher.pending_count == 0

    def test_is_ready_false(self):
        batcher = NotificationBatcher(window_seconds=600)
        batcher.add(_event())
        now = datetime.now(timezone.utc)
        assert batcher.is_ready(now) is False

    def test_is_ready_true(self):
        batcher = NotificationBatcher(window_seconds=1)
        batcher.add(_event())
        future = datetime.now(timezone.utc) + timedelta(seconds=5)
        assert batcher.is_ready(future) is True

    def test_is_ready_empty(self):
        batcher = NotificationBatcher(window_seconds=0)
        assert batcher.is_ready() is False

    def test_flush(self):
        batcher = NotificationBatcher(window_seconds=60)
        batcher.add(_event())
        events = batcher.flush()
        assert len(events) == 1
        assert batcher.pending_count == 0

    def test_clear(self):
        batcher = NotificationBatcher(window_seconds=60)
        batcher.add(_event())
        batcher.clear()
        assert batcher.pending_count == 0


# ---------------------------------------------------------------------------
# NotificationHistory
# ---------------------------------------------------------------------------


class TestNotificationHistory:
    def test_record(self):
        h = NotificationHistory()
        h.record(_event())
        assert h.count == 1

    def test_is_duplicate(self):
        h = NotificationHistory()
        e = _event()
        h.record(e)
        assert h.is_duplicate(e) is True

    def test_not_duplicate(self):
        h = NotificationHistory()
        h.record(_event(rule_id="A"))
        assert h.is_duplicate(_event(rule_id="B")) is False

    def test_clear(self):
        h = NotificationHistory()
        h.record(_event())
        h.clear()
        assert h.count == 0
        assert h.is_duplicate(_event()) is False

    def test_to_dict(self):
        h = NotificationHistory()
        h.record(_event())
        d = h.to_dict()
        assert d["total"] == 1
        assert len(d["sent"]) == 1

    def test_max_entries(self):
        h = NotificationHistory(max_entries=2)
        h.record(_event(rule_id="A"))
        h.record(_event(rule_id="B"))
        h.record(_event(rule_id="C"))
        assert h.count == 2


# ---------------------------------------------------------------------------
# is_quiet_hours
# ---------------------------------------------------------------------------


class TestQuietHours:
    def test_disabled(self):
        cfg = NotificationConfig()
        assert is_quiet_hours(cfg) is False

    def test_in_quiet_hours(self):
        cfg = NotificationConfig(
            quiet_hours_start=22, quiet_hours_end=6,
        )
        now = datetime(2025, 1, 1, 23, 0, tzinfo=timezone.utc)
        assert is_quiet_hours(cfg, now) is True

    def test_not_in_quiet_hours(self):
        cfg = NotificationConfig(
            quiet_hours_start=22, quiet_hours_end=6,
        )
        now = datetime(2025, 1, 1, 12, 0, tzinfo=timezone.utc)
        assert is_quiet_hours(cfg, now) is False

    def test_simple_range(self):
        cfg = NotificationConfig(
            quiet_hours_start=9, quiet_hours_end=17,
        )
        now = datetime(2025, 1, 1, 12, 0, tzinfo=timezone.utc)
        assert is_quiet_hours(cfg, now) is True

    def test_simple_range_outside(self):
        cfg = NotificationConfig(
            quiet_hours_start=9, quiet_hours_end=17,
        )
        now = datetime(2025, 1, 1, 20, 0, tzinfo=timezone.utc)
        assert is_quiet_hours(cfg, now) is False

    def test_wrap_midnight_early_morning(self):
        cfg = NotificationConfig(
            quiet_hours_start=22, quiet_hours_end=6,
        )
        now = datetime(2025, 1, 1, 3, 0, tzinfo=timezone.utc)
        assert is_quiet_hours(cfg, now) is True


# ---------------------------------------------------------------------------
# filter_events
# ---------------------------------------------------------------------------


class TestFilterEvents:
    def test_basic_filter(self):
        events = [_event(severity="HIGH"), _event(severity="LOW")]
        cfg = NotificationConfig(severity_threshold="HIGH")
        result = filter_events(events, cfg)
        assert len(result) == 1

    def test_disabled(self):
        events = [_event()]
        cfg = NotificationConfig(enabled=False)
        result = filter_events(events, cfg)
        assert result == []

    def test_quiet_hours(self):
        events = [_event()]
        cfg = NotificationConfig(
            quiet_hours_start=0, quiet_hours_end=23,
        )
        now = datetime(2025, 1, 1, 12, 0, tzinfo=timezone.utc)
        result = filter_events(events, cfg, now=now)
        assert result == []

    def test_dedup(self):
        events = [_event(), _event()]
        cfg = NotificationConfig()
        history = NotificationHistory()
        history.record(events[0])
        result = filter_events(events, cfg, history=history)
        assert len(result) == 0

    def test_no_filters(self):
        events = [_event()]
        cfg = NotificationConfig(severity_threshold="INFO")
        result = filter_events(events, cfg)
        assert len(result) == 1
