"""Tests for channel sender blocking and ghost message filtering in FrameHandler."""

import time
from unittest.mock import MagicMock

from meshy.frame_handler import FrameHandler
from meshy.models import Channel, NotificationLevel


def _make_channel(index=0, name="General", psk=None):
    return Channel(
        index=index,
        name=name,
        psk=psk or bytes(range(16)),
        notification_level=NotificationLevel.NONE,
    )


def _make_win(storage):
    win = MagicMock()
    win._storage = storage
    win._self_info = {"name": "Me"}
    win._channels_view.on_path_variant_received.return_value = False
    win._channels = [_make_channel()]
    win._channels_view_inst = None
    win._conn_ctrl.channel_sync_index = -1
    return win


def _make_msg(sender_name, channel_idx=0, text="hello"):
    return {
        "sender_name": sender_name,
        "msg_text": text,
        "sender_timestamp": time.time(),
        "channel_idx": channel_idx,
    }


class TestBlockedSenderFiltering:
    def test_blocked_message_is_dropped(self, storage):
        win = _make_win(storage)
        fh = FrameHandler(win)
        storage.block_channel_sender("Spammer")
        fh.load_blocked_senders()

        storage.save_channel_message = MagicMock()
        fh.handle_incoming_channel_message(_make_msg("Spammer"))

        storage.save_channel_message.assert_not_called()
        assert not win._channels_view.on_channel_message_received.called

    def test_unblocked_message_is_saved(self, storage):
        win = _make_win(storage)
        fh = FrameHandler(win)
        fh.load_blocked_senders()

        fh.handle_incoming_channel_message(_make_msg("Alice"))

        win._channels_view.on_channel_message_received.assert_called_once()

    def test_update_blocked_senders_reflects_changes(self, storage):
        win = _make_win(storage)
        fh = FrameHandler(win)
        fh.load_blocked_senders()
        assert fh._blocked_senders == set()

        storage.block_channel_sender("Spammer")
        fh.update_blocked_senders()
        assert "Spammer" in fh._blocked_senders

        storage.unblock_channel_sender("Spammer")
        fh.update_blocked_senders()
        assert "Spammer" not in fh._blocked_senders

    def test_echo_not_affected_by_block(self, storage):
        win = _make_win(storage)
        fh = FrameHandler(win)
        storage.block_channel_sender("Me")
        fh.load_blocked_senders()

        fh.handle_incoming_channel_message(_make_msg("Me"))

        win._channels_view.on_repeat_received.assert_called_once()
        win._channels_view.on_channel_message_received.assert_not_called()

    def test_empty_sender_name_not_blocked(self, storage):
        win = _make_win(storage)
        fh = FrameHandler(win)
        storage.block_channel_sender("")
        fh.load_blocked_senders()

        fh.handle_incoming_channel_message(_make_msg(""))

        win._channels_view.on_channel_message_received.assert_called_once()


class TestGhostMessageFiltering:
    """Guard against firmware delivering messages for unconfigured channels.

    Empty channel slots (all-zero PSK) get hash 0x37 in the firmware and can
    receive crafted group messages.  After channel sync completes Meshy must
    drop these.
    """

    def test_message_for_unconfigured_channel_dropped(self, storage):
        win = _make_win(storage)
        fh = FrameHandler(win)
        fh.load_blocked_senders()

        fh.handle_incoming_channel_message(_make_msg("Alice", channel_idx=5))

        win._channels_view.on_channel_message_received.assert_not_called()

    def test_message_for_configured_channel_accepted(self, storage):
        win = _make_win(storage)
        fh = FrameHandler(win)
        fh.load_blocked_senders()

        fh.handle_incoming_channel_message(_make_msg("Alice", channel_idx=0))

        win._channels_view.on_channel_message_received.assert_called_once()

    def test_empty_channel_slot_treated_as_unconfigured(self, storage):
        """A slot with empty name and zero PSK must not accept messages."""
        win = _make_win(storage)
        ghost = Channel.empty(3)
        win._channels.append(ghost)
        fh = FrameHandler(win)
        fh.load_blocked_senders()

        fh.handle_incoming_channel_message(_make_msg("Alice", channel_idx=3))

        win._channels_view.on_channel_message_received.assert_not_called()

    def test_guard_inactive_during_channel_sync(self, storage):
        """During sync, messages pass through even for unknown indices."""
        win = _make_win(storage)
        win._conn_ctrl.channel_sync_index = 2  # sync in progress
        fh = FrameHandler(win)
        fh.load_blocked_senders()

        fh.handle_incoming_channel_message(_make_msg("Alice", channel_idx=5))

        win._channels_view.on_channel_message_received.assert_called_once()
