"""Tests for Phase 2.2 inhibitor and grouper."""

import pytest
from unittest.mock import Mock

from meshai.notifications.events import Event
from meshai.notifications.pipeline.inhibitor import Inhibitor
from meshai.notifications.pipeline.grouper import Grouper


def make_event(id, severity, inhibit_keys=None, group_key=None):
    return Event(
        id=id,
        source="test",
        category="test_cat",
        severity=severity,
        title=f"Event {id}",
        inhibit_keys=inhibit_keys or [],
        group_key=group_key,
    )


# ===================== INHIBITOR TESTS =====================

class TestInhibitor:

    def test_event_without_inhibit_keys_passes_through(self):
        next_handler = Mock()
        inhibitor = Inhibitor(next_handler)
        event = make_event("e1", "immediate", inhibit_keys=[])
        inhibitor.handle(event)
        next_handler.assert_called_once_with(event)

    def test_lower_severity_after_higher_is_suppressed(self):
        next_handler = Mock()
        inhibitor = Inhibitor(next_handler)
        ev1 = make_event("e1", "immediate", inhibit_keys=["battery:NODE1"])
        ev2 = make_event("e2", "priority", inhibit_keys=["battery:NODE1"])
        inhibitor.handle(ev1)
        inhibitor.handle(ev2)
        assert next_handler.call_count == 1
        next_handler.assert_called_once_with(ev1)

    def test_equal_severity_is_suppressed(self):
        next_handler = Mock()
        inhibitor = Inhibitor(next_handler)
        ev1 = make_event("e1", "priority", inhibit_keys=["key1"])
        ev2 = make_event("e2", "priority", inhibit_keys=["key1"])
        inhibitor.handle(ev1)
        inhibitor.handle(ev2)
        assert next_handler.call_count == 1

    def test_higher_severity_after_lower_passes_and_upgrades(self):
        next_handler = Mock()
        inhibitor = Inhibitor(next_handler)
        ev1 = make_event("e1", "priority", inhibit_keys=["key1"])
        ev2 = make_event("e2", "immediate", inhibit_keys=["key1"])
        inhibitor.handle(ev1)
        inhibitor.handle(ev2)
        assert next_handler.call_count == 2
        keys = inhibitor.active_keys()
        assert keys["key1"][0] == 2  # immediate rank

    def test_inhibit_key_expires_after_ttl(self):
        next_handler = Mock()
        inhibitor = Inhibitor(next_handler, ttl_seconds=10)
        current_time = [0.0]
        inhibitor._now = lambda: current_time[0]

        ev1 = make_event("e1", "immediate", inhibit_keys=["key1"])
        current_time[0] = 0.0
        inhibitor.handle(ev1)

        current_time[0] = 15.0
        ev2 = make_event("e2", "routine", inhibit_keys=["key1"])
        inhibitor.handle(ev2)

        assert next_handler.call_count == 2

    def test_multiple_keys_any_active_suppresses(self):
        next_handler = Mock()
        inhibitor = Inhibitor(next_handler)

        ev1 = make_event("e1", "immediate", inhibit_keys=["a", "b"])
        inhibitor.handle(ev1)
        assert next_handler.call_count == 1

        ev2 = make_event("e2", "routine", inhibit_keys=["b", "c"])
        inhibitor.handle(ev2)
        assert next_handler.call_count == 1  # suppressed by "b"

        ev3 = make_event("e3", "routine", inhibit_keys=["c", "d"])
        inhibitor.handle(ev3)
        assert next_handler.call_count == 2  # passes, no active key


# ===================== GROUPER TESTS =====================

class TestGrouper:

    def test_event_without_group_key_emits_immediately(self):
        next_handler = Mock()
        grouper = Grouper(next_handler)
        event = make_event("e1", "immediate", group_key=None)
        grouper.handle(event)
        next_handler.assert_called_once_with(event)

    def test_event_with_group_key_is_held_not_emitted(self):
        next_handler = Mock()
        grouper = Grouper(next_handler)
        event = make_event("e1", "priority", group_key="fire:42")
        grouper.handle(event)
        next_handler.assert_not_called()
        assert grouper.held_count() == 1

    def test_second_same_group_key_replaces_first(self):
        next_handler = Mock()
        grouper = Grouper(next_handler)
        ev1 = make_event("e1", "priority", group_key="fire:42")
        ev2 = make_event("e2", "priority", group_key="fire:42")
        grouper.handle(ev1)
        grouper.handle(ev2)
        next_handler.assert_not_called()
        assert grouper.held_count() == 1
        grouper.flush_all()
        assert next_handler.call_count == 1
        emitted_event = next_handler.call_args[0][0]
        assert emitted_event.id == "e2"

    def test_tick_emits_when_window_expired(self):
        next_handler = Mock()
        grouper = Grouper(next_handler, window_seconds=5)
        current_time = [0.0]
        grouper._now = lambda: current_time[0]

        current_time[0] = 0.0
        event = make_event("e1", "priority", group_key="g")
        grouper.handle(event)
        assert grouper.held_count() == 1

        current_time[0] = 3.0
        grouper.tick()
        next_handler.assert_not_called()
        assert grouper.held_count() == 1

        current_time[0] = 10.0
        grouper.tick()
        next_handler.assert_called_once()
        assert grouper.held_count() == 0

    def test_flush_all_emits_everything_immediately(self):
        next_handler = Mock()
        grouper = Grouper(next_handler)
        ev1 = make_event("e1", "priority", group_key="g1")
        ev2 = make_event("e2", "priority", group_key="g2")
        ev3 = make_event("e3", "priority", group_key="g3")
        grouper.handle(ev1)
        grouper.handle(ev2)
        grouper.handle(ev3)
        assert grouper.held_count() == 3
        grouper.flush_all()
        assert next_handler.call_count == 3
        assert grouper.held_count() == 0


# ===================== INTEGRATION TEST =====================

class TestInhibitorGrouperChain:

    def test_inhibitor_then_grouper_chain(self):
        terminal = Mock()
        grouper = Grouper(next_handler=terminal)
        inhibitor = Inhibitor(next_handler=grouper.handle)

        # Send priority event with group_key and inhibit_keys (immediate would bypass)
        ev1 = make_event("e1", "priority", group_key="g1", inhibit_keys=["k1"])
        inhibitor.handle(ev1)
        # After inhibitor: passed (no prior key)
        # After grouper: held (group_key present)
        terminal.assert_not_called()
        assert grouper.held_count() == 1

        # Send routine event with same group_key and inhibit_keys
        ev2 = make_event("e2", "routine", group_key="g1", inhibit_keys=["k1"])
        inhibitor.handle(ev2)
        # After inhibitor: SUPPRESSED (k1 active at higher rank)
        terminal.assert_not_called()
        assert grouper.held_count() == 1  # still 1, not 2

        # Flush grouper
        grouper.flush_all()
        terminal.assert_called_once()
        emitted = terminal.call_args[0][0]
        assert emitted.id == "e1"  # the priority event, not the suppressed routine
