import asyncio
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from repeater.main import RepeaterDaemon


class _FakeLocalIdentity:
    def __init__(self, seed: bytes):
        self._seed = seed

    def get_public_key(self):
        # Keep deterministic first-byte hash behavior.
        return bytes([self._seed[0]]) + (b"P" * 31)

    def get_address_bytes(self):
        return b"\xab\xcd"


def _base_config():
    return {
        "repeater": {"node_name": "n1", "mode": "forward", "identity_key": b"k" * 32},
        "logging": {"level": "INFO"},
        "http": {"host": "127.0.0.1", "port": 8123},
    }


@pytest.mark.asyncio
async def test_load_additional_identities_valid_and_invalid_entries():
    cfg = _base_config()
    cfg["identities"] = {
        "room_servers": [
            {},  # missing fields
            {"name": "bad-hex", "identity_key": "zz-not-hex"},
            {"name": "bad-len", "identity_key": "aa"},
            {"name": "bad-type", "identity_key": 12345},
            {"name": "good-bytes", "identity_key": b"\x10" * 32},
            {"name": "good-hex", "identity_key": ("11" * 32)},
            {"name": "good-hex-64", "identity_key": ("22" * 64)},
        ]
    }

    daemon = RepeaterDaemon(cfg, radio=object())
    daemon.identity_manager = SimpleNamespace(list_identities=lambda: [1, 2])
    daemon._register_identity_everywhere = MagicMock(return_value=True)

    with patch("pymc_core.LocalIdentity", _FakeLocalIdentity):
        await daemon._load_additional_identities()

    # Only valid entries should be registered (including 64-byte firmware keys).
    assert daemon._register_identity_everywhere.call_count == 3
    names = [c.kwargs["name"] for c in daemon._register_identity_everywhere.call_args_list]
    assert names == ["good-bytes", "good-hex", "good-hex-64"]


@pytest.mark.asyncio
async def test_run_starts_http_and_handles_dispatcher_cancelled_gracefully():
    daemon = RepeaterDaemon(_base_config(), radio=SimpleNamespace(cleanup=MagicMock()))

    async def _init_stub():
        daemon.local_identity = SimpleNamespace(get_public_key=lambda: b"\x22" * 32)
        daemon.dispatcher = SimpleNamespace(
            run_forever=AsyncMock(side_effect=asyncio.CancelledError())
        )

    daemon.initialize = _init_stub

    fake_http_instance = SimpleNamespace(start=MagicMock(), stop=MagicMock())

    fake_loop_for_signals = SimpleNamespace(add_signal_handler=MagicMock())

    with (
        patch("asyncio.get_running_loop", return_value=fake_loop_for_signals),
        patch("repeater.main.HTTPStatsServer", return_value=fake_http_instance),
        patch("os.path.exists", return_value=False),
    ):
        await daemon.run()

    fake_http_instance.start.assert_called_once()
    daemon.dispatcher.run_forever.assert_awaited_once()
