"""Tests for PeerDrop CLI client."""

import tempfile
from pathlib import Path
from unittest.mock import MagicMock

import pytest
import trio

from peerdrop.core.service import ServiceInterface, IdentityInfo, PeerInfo, TransferInfo
from peerdrop.daemon.protocol import make_response
from peerdrop.daemon.server import DaemonServer
from peerdrop.utils.framing import length_prefix_pack, length_prefix_unpack
from peerdrop.interfaces.cli.client import DaemonClient


def _make_mock_service(peer_id: str = "QmTest", addrs: list[str] | None = None) -> MagicMock:
    """Create a mock ServiceInterface with the expected methods."""
    mock = MagicMock(spec=ServiceInterface)
    mock.get_identity.return_value = IdentityInfo(peer_id=peer_id, addrs=addrs or [])
    mock.discover_peers.return_value = []
    mock.list_transfers.return_value = []
    return mock


class TestLengthPrefix:
    def test_pack_unpack_roundtrip(self):
        data = b"test message"
        packed = length_prefix_pack(data)
        result = length_prefix_unpack(packed)
        assert result is not None
        unpacked, remaining = result
        assert unpacked == data
        assert remaining == b""

    def test_incomplete_message(self):
        packed = length_prefix_pack(b"hello")
        result = length_prefix_unpack(packed[:3])
        assert result is None

    def test_empty_buffer(self):
        result = length_prefix_unpack(b"")
        assert result is None


@pytest.mark.trio
async def test_client_connect_disconnect():
    """Test client can connect and disconnect cleanly."""
    service = _make_mock_service()

    with tempfile.TemporaryDirectory() as tmpdir:
        sock_path = Path(tmpdir) / "test.sock"
        server = DaemonServer(service, sock_path=sock_path)

        async with trio.open_nursery() as nursery:
            await nursery.start(server.serve)

            client = DaemonClient(sock_path)
            await client.connect()
            assert client._stream is not None

            await client.close()
            assert client._stream is None

            nursery.cancel_scope.cancel()


@pytest.mark.trio
async def test_client_health():
    """Test client health request."""
    service = _make_mock_service(peer_id="QmTestPeer")

    with tempfile.TemporaryDirectory() as tmpdir:
        sock_path = Path(tmpdir) / "test.sock"
        server = DaemonServer(service, sock_path=sock_path)

        async with trio.open_nursery() as nursery:
            await nursery.start(server.serve)

            client = DaemonClient(sock_path)
            await client.connect()

            result = await client.health()
            assert result["ok"] is True
            assert result["data"]["status"] == "ok"
            assert result["data"]["peer_id"] == "QmTestPeer"

            await client.close()
            nursery.cancel_scope.cancel()


@pytest.mark.trio
async def test_client_get_identity():
    """Test client get_identity request."""
    service = _make_mock_service(
        peer_id="QmMyDevice",
        addrs=["/ip4/127.0.0.1/tcp/4001"],
    )

    with tempfile.TemporaryDirectory() as tmpdir:
        sock_path = Path(tmpdir) / "test.sock"
        server = DaemonServer(service, sock_path=sock_path)

        async with trio.open_nursery() as nursery:
            await nursery.start(server.serve)

            client = DaemonClient(sock_path)
            await client.connect()

            result = await client.get_identity()
            assert result["ok"] is True
            assert result["data"]["peer_id"] == "QmMyDevice"
            assert "/ip4/127.0.0.1/tcp/4001" in result["data"]["addrs"]

            await client.close()
            nursery.cancel_scope.cancel()


@pytest.mark.trio
async def test_client_discover_peers():
    """Test client discover_peers request."""
    service = _make_mock_service()

    with tempfile.TemporaryDirectory() as tmpdir:
        sock_path = Path(tmpdir) / "test.sock"
        server = DaemonServer(service, sock_path=sock_path)

        async with trio.open_nursery() as nursery:
            await nursery.start(server.serve)

            client = DaemonClient(sock_path)
            await client.connect()

            peers = await client.discover_peers()
            assert peers == []

            await client.close()
            nursery.cancel_scope.cancel()


@pytest.mark.trio
async def test_client_list_transfers():
    """Test client list_transfers request."""
    service = _make_mock_service()

    with tempfile.TemporaryDirectory() as tmpdir:
        sock_path = Path(tmpdir) / "test.sock"
        server = DaemonServer(service, sock_path=sock_path)

        async with trio.open_nursery() as nursery:
            await nursery.start(server.serve)

            client = DaemonClient(sock_path)
            await client.connect()

            transfers = await client.list_transfers()
            assert transfers == []

            await client.close()
            nursery.cancel_scope.cancel()


@pytest.mark.trio
async def test_client_multiple_requests():
    """Test client can send multiple sequential requests."""
    service = _make_mock_service(addrs=["/ip4/127.0.0.1/tcp/4001"])

    with tempfile.TemporaryDirectory() as tmpdir:
        sock_path = Path(tmpdir) / "test.sock"
        server = DaemonServer(service, sock_path=sock_path)

        async with trio.open_nursery() as nursery:
            await nursery.start(server.serve)

            client = DaemonClient(sock_path)
            await client.connect()

            # Multiple requests on same connection
            r1 = await client.health()
            assert r1["ok"] is True

            r2 = await client.get_identity()
            assert r2["ok"] is True

            r3 = await client.discover_peers()
            assert isinstance(r3, list)

            r4 = await client.list_transfers()
            assert isinstance(r4, list)

            await client.close()
            nursery.cancel_scope.cancel()


@pytest.mark.trio
async def test_client_not_connected():
    """Test client raises error when not connected."""
    client = DaemonClient()
    with pytest.raises(ConnectionError, match="Not connected"):
        await client.health()


@pytest.mark.trio
async def test_client_daemon_not_running():
    """Test client raises error when daemon not running."""
    client = DaemonClient("/nonexistent/path.sock")
    with pytest.raises(ConnectionError, match="Daemon not running"):
        await client.connect()
