"""Tests for HandoffTransport — Handoff 传输层抽象"""

from __future__ import annotations

import asyncio
from unittest.mock import AsyncMock, patch

from agentkit.core.handoff_transport import (
    InProcessHandoffTransport,
    RedisHandoffTransport,
)


# ── InProcessHandoffTransport Tests ─────────────────────────────


class TestInProcessHandoffTransport:
    """InProcessHandoffTransport 单元测试"""

    async def test_send_and_receive(self):
        """测试基本的消息发送和接收"""
        transport = InProcessHandoffTransport()

        # 启动监听协程
        received: list[dict] = []

        async def consumer():
            async for msg in transport.listen("test-channel"):
                received.append(msg)
                break  # 收到一条后退出

        consumer_task = asyncio.create_task(consumer())

        # 给消费者一点时间启动
        await asyncio.sleep(0.05)

        await transport.send("test-channel", {"task_id": "t1", "action": "handoff"})

        await asyncio.wait_for(consumer_task, timeout=1.0)

        assert len(received) == 1
        assert received[0] == {"task_id": "t1", "action": "handoff"}

    async def test_broadcast_to_multiple_consumers(self):
        """测试广播到多个消费者"""
        transport = InProcessHandoffTransport()

        received_a: list[dict] = []
        received_b: list[dict] = []

        async def consumer_a():
            async for msg in transport.listen("broadcast-ch"):
                received_a.append(msg)
                if len(received_a) >= 2:
                    break

        async def consumer_b():
            async for msg in transport.listen("broadcast-ch"):
                received_b.append(msg)
                if len(received_b) >= 2:
                    break

        task_a = asyncio.create_task(consumer_a())
        task_b = asyncio.create_task(consumer_b())

        # 给消费者启动时间
        await asyncio.sleep(0.05)

        await transport.send("broadcast-ch", {"seq": 1})
        await transport.send("broadcast-ch", {"seq": 2})

        await asyncio.wait_for(task_a, timeout=1.0)
        await asyncio.wait_for(task_b, timeout=1.0)

        assert len(received_a) == 2
        assert len(received_b) == 2
        assert received_a[0] == {"seq": 1}
        assert received_b[1] == {"seq": 2}

    async def test_register_handler_called_on_send(self):
        """测试注册的处理器在 send 时被调用"""
        transport = InProcessHandoffTransport()

        handler_calls: list[dict] = []

        async def handler(message: dict) -> None:
            handler_calls.append(message)

        transport.register_handler("handler-ch", handler)

        await transport.send("handler-ch", {"task_id": "t1"})
        await transport.send("handler-ch", {"task_id": "t2"})

        assert len(handler_calls) == 2
        assert handler_calls[0] == {"task_id": "t1"}
        assert handler_calls[1] == {"task_id": "t2"}

    async def test_listen_blocks_when_queue_empty(self):
        """测试队列为空时 listen 阻塞等待"""
        transport = InProcessHandoffTransport()

        received: list[dict] = []

        async def consumer():
            async for msg in transport.listen("empty-ch"):
                received.append(msg)
                break

        task = asyncio.create_task(consumer())
        await asyncio.sleep(0.05)

        # 此时不应有消息
        assert len(received) == 0

        # 发送消息后消费者应收到
        await transport.send("empty-ch", {"hello": "world"})
        await asyncio.wait_for(task, timeout=1.0)

        assert len(received) == 1

    async def test_close_clears_state(self):
        """测试 close 清理所有状态"""
        transport = InProcessHandoffTransport()

        async def handler(message: dict) -> None:
            pass

        transport.register_handler("ch1", handler)
        transport.register_handler("ch2", handler)

        assert len(transport._handlers) == 2
        assert len(transport._handlers["ch1"]) == 1
        assert len(transport._handlers["ch2"]) == 1

        transport.close()

        assert len(transport._handlers) == 0
        assert len(transport._channels) == 0

    async def test_send_to_nonexistent_channel_no_raise(self):
        """测试向不存在的频道发送消息不抛异常（消息丢弃）"""
        transport = InProcessHandoffTransport()

        # 不应有任何异常
        await transport.send("nonexistent-ch", {"data": "test"})

    async def test_handler_error_does_not_propagate(self):
        """测试处理器抛出异常不影响其他处理器和 send"""
        transport = InProcessHandoffTransport()

        good_calls: list[dict] = []

        async def bad_handler(message: dict) -> None:
            raise ValueError("handler error")

        async def good_handler(message: dict) -> None:
            good_calls.append(message)

        transport.register_handler("err-ch", bad_handler)
        transport.register_handler("err-ch", good_handler)

        # send 不应抛出异常
        await transport.send("err-ch", {"task_id": "t1"})

        # good_handler 仍应被调用
        assert len(good_calls) == 1
        assert good_calls[0] == {"task_id": "t1"}


# ── RedisHandoffTransport Tests ─────────────────────────────────


class TestRedisHandoffTransport:
    """RedisHandoffTransport 单元测试（不依赖真实 Redis）"""

    def test_initialization_with_redis_url(self):
        """测试使用 redis_url 初始化"""
        transport = RedisHandoffTransport(redis_url="redis://localhost:6379")

        assert transport._redis_url == "redis://localhost:6379"
        assert transport._redis is None
        assert len(transport._active_pubsubs) == 0
        assert len(transport._handlers) == 0
        assert len(transport._listen_tasks) == 0

    def test_lazy_connection_no_connection_on_init(self):
        """测试懒连接：初始化时不建立 Redis 连接"""
        transport = RedisHandoffTransport(redis_url="redis://localhost:6379")

        # 初始化后不应有任何连接
        assert transport._redis is None
        assert len(transport._active_pubsubs) == 0

    async def test_send_calls_ensure_connection(self):
        """测试 send 触发懒连接"""
        transport = RedisHandoffTransport(redis_url="redis://localhost:6379")

        mock_redis = AsyncMock()
        mock_redis.ping = AsyncMock()
        mock_redis.publish = AsyncMock()

        with patch("agentkit.core.handoff_transport.aioredis") as mock_aioredis:
            mock_aioredis.from_url.return_value = mock_redis
            await transport.send("test-ch", {"task_id": "t1"})

            # 验证 from_url 被调用
            mock_aioredis.from_url.assert_called_once_with(
                "redis://localhost:6379", decode_responses=True
            )
            # 验证 ping 被调用
            mock_redis.ping.assert_called_once()
            # 验证 publish 被调用
            mock_redis.publish.assert_called_once()

    async def test_register_handler_creates_listen_task(self):
        """测试 register_handler 启动后台监听任务"""
        transport = RedisHandoffTransport(redis_url="redis://localhost:6379")

        async def handler(message: dict) -> None:
            pass

        transport.register_handler("test-ch", handler)

        assert "test-ch" in transport._listen_tasks
        assert not transport._listen_tasks["test-ch"].done()
        assert len(transport._handlers["test-ch"]) == 1

        # 清理
        await transport.close()

    async def test_close_cancels_tasks_and_clears_state(self):
        """测试 close 取消监听任务并清理状态"""
        transport = RedisHandoffTransport(redis_url="redis://localhost:6379")

        async def handler(message: dict) -> None:
            pass

        transport.register_handler("ch1", handler)

        mock_redis = AsyncMock()
        mock_redis.close = AsyncMock()
        transport._redis = mock_redis

        await transport.close()

        assert len(transport._handlers) == 0
        assert len(transport._listen_tasks) == 0
        assert transport._redis is None
        mock_redis.close.assert_called_once()
