"""Tests for PeerDrop Daemon handler module."""

import pytest
import trio
from unittest.mock import MagicMock, AsyncMock

from peerdrop.core.service import ServiceInterface, IdentityInfo, PeerInfo, TransferInfo
from peerdrop.core.models import Peer, Transfer, TransferStatus
from peerdrop.daemon.handler import RequestHandler, VALID_ACTIONS
from peerdrop.daemon.protocol import make_request, unpack_response


def _make_handler(service: ServiceInterface | None = None) -> RequestHandler:
    """Create a RequestHandler with a mock service."""
    if service is None:
        service = MagicMock(spec=ServiceInterface)
        service.get_identity.return_value = IdentityInfo(
            peer_id="QmLocalPeer",
            addrs=["/ip4/127.0.0.1/tcp/4001"],
        )
        service.discover_peers.return_value = []
        service.get_peer.return_value = None
        service.list_transfers.return_value = []
        service.get_transfer.return_value = None
    return RequestHandler(service)


class TestHandlerValidation:
    @pytest.mark.trio
    async def test_invalid_json(self):
        handler = _make_handler()
        response = await handler.handle(b"not valid msgpack")
        msg = unpack_response(response)
        assert msg["ok"] is False
        assert "Invalid message" in msg["error"]

    @pytest.mark.trio
    async def test_unknown_action(self):
        handler = _make_handler()
        request = make_request("nonexistent_action", request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is False
        assert "Unknown action" in msg["error"]

    def test_valid_actions_exist(self):
        assert "discover_peers" in VALID_ACTIONS
        assert "send_file" in VALID_ACTIONS
        assert "health" in VALID_ACTIONS
        assert "connect_peer" in VALID_ACTIONS
        assert len(VALID_ACTIONS) == 17


class TestDiscoverPeers:
    @pytest.mark.trio
    async def test_empty_peers(self):
        handler = _make_handler()
        request = make_request("discover_peers", request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is True
        assert msg["data"]["peers"] == []

    @pytest.mark.trio
    async def test_with_peers(self):
        service = MagicMock(spec=ServiceInterface)
        service.discover_peers.return_value = [
            PeerInfo(peer_id="QmPeer1", addrs=["/ip4/1.2.3.4/tcp/4001"], name="Device1", is_online=True),
            PeerInfo(peer_id="QmPeer2", addrs=["/ip4/5.6.7.8/tcp/4002"], name="Device2", is_online=True),
        ]
        handler = _make_handler(service)
        request = make_request("discover_peers", request_id="r2")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is True
        assert len(msg["data"]["peers"]) == 2
        assert msg["data"]["peers"][0]["peer_id"] == "QmPeer1"


class TestGetPeer:
    @pytest.mark.trio
    async def test_missing_peer_id(self):
        handler = _make_handler()
        request = make_request("get_peer", params={}, request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is False
        assert "Missing" in msg["error"]

    @pytest.mark.trio
    async def test_peer_not_found(self):
        handler = _make_handler()
        request = make_request("get_peer", params={"peer_id": "QmNonexistent"}, request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is False
        assert "not found" in msg["error"]

    @pytest.mark.trio
    async def test_peer_found(self):
        service = MagicMock(spec=ServiceInterface)
        service.get_peer.return_value = PeerInfo(
            peer_id="QmFound", addrs=["/ip4/1.1.1.1/tcp/4001"], name="Found Device", is_online=True
        )
        handler = _make_handler(service)
        request = make_request("get_peer", params={"peer_id": "QmFound"}, request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is True
        assert msg["data"]["peer_id"] == "QmFound"


class TestSendFile:
    @pytest.mark.trio
    async def test_missing_file(self):
        handler = _make_handler()
        request = make_request("send_file", params={"target": "Qm123"}, request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is False
        assert "Missing 'file'" in msg["error"]

    @pytest.mark.trio
    async def test_missing_target(self):
        handler = _make_handler()
        request = make_request("send_file", params={"file": "test.txt"}, request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is False
        assert "Missing 'target'" in msg["error"]


class TestListTransfers:
    @pytest.mark.trio
    async def test_empty_list(self):
        handler = _make_handler()
        request = make_request("list_transfers", request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is True
        assert msg["data"]["transfers"] == []


class TestGetTransfer:
    @pytest.mark.trio
    async def test_missing_transfer_id(self):
        handler = _make_handler()
        request = make_request("get_transfer", params={}, request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is False
        assert "Missing" in msg["error"]

    @pytest.mark.trio
    async def test_transfer_not_found(self):
        handler = _make_handler()
        request = make_request("get_transfer", params={"transfer_id": "xyz"}, request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is False
        assert "not found" in msg["error"]


class TestGetIdentity:
    @pytest.mark.trio
    async def test_identity(self):
        handler = _make_handler()
        request = make_request("get_identity", request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is True
        assert msg["data"]["peer_id"] == "QmLocalPeer"
        assert "/ip4/127.0.0.1/tcp/4001" in msg["data"]["addrs"]


class TestHealth:
    @pytest.mark.trio
    async def test_health_check(self):
        handler = _make_handler()
        request = make_request("health", request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is True
        assert msg["data"]["status"] == "ok"
        assert msg["data"]["peer_id"] == "QmLocalPeer"


class TestConnectPeer:
    @pytest.mark.trio
    async def test_missing_addr(self):
        handler = _make_handler()
        request = make_request("connect_peer", params={}, request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is False
        assert "Missing" in msg["error"]

    @pytest.mark.trio
    async def test_connect_success(self):
        service = MagicMock(spec=ServiceInterface)

        async def fake_connect(addr):
            return PeerInfo(peer_id="QmNew", addrs=[addr], name="", is_online=True)

        service.connect_peer = fake_connect
        handler = _make_handler(service)
        request = make_request("connect_peer", params={"addr": "/ip4/1.2.3.4/tcp/4001/p2p/QmNew"}, request_id="r1")
        response = await handler.handle(request)
        msg = unpack_response(response)
        assert msg["ok"] is True
        assert msg["data"]["peer_id"] == "QmNew"
