"""F10 ActiveAgentCounter 单元测试

按 test-plan-v2.6.md §F10：
- T1: 全局上限（P0）
- T2: per-agent 串行（P0）
- T3: release 恢复（P0）
- T4: 并发竞争（P1）
"""

import asyncio
import pytest

pytestmark = pytest.mark.unit

from src.daemon.counter import ActiveAgentCounter


@pytest.fixture
def counter():
    return ActiveAgentCounter(max_global=3, max_per_agent=1)


# ---------------------------------------------------------------------------
# T1: 全局上限
# ---------------------------------------------------------------------------

class TestGlobalLimit:
    def test_acquire_within_limit(self, counter):
        """全局未满 → 成功"""
        result = asyncio.run(counter.acquire("agent-1"))
        assert result is True
        assert counter.global_active == 1

    def test_acquire_fills_global(self, counter):
        """填满全局"""
        for i in range(3):
            asyncio.run(counter.acquire(f"agent-{i}"))
        assert counter.global_active == 3

    def test_acquire_exceeds_global(self, counter):
        """超出全局上限 → 失败"""
        for i in range(3):
            asyncio.run(counter.acquire(f"agent-{i}"))
        result = asyncio.run(counter.acquire("agent-extra"))
        assert result is False

    def test_can_acquire_respects_limit(self, counter):
        """can_acquire 检查全局上限"""
        assert asyncio.run(counter.can_acquire("agent-1")) is True
        for i in range(3):
            asyncio.run(counter.acquire(f"agent-{i}"))
        assert asyncio.run(counter.can_acquire("agent-extra")) is False


# ---------------------------------------------------------------------------
# T2: per-agent 串行
# ---------------------------------------------------------------------------

class TestPerAgentSerial:
    def test_same_agent_blocked(self, counter):
        """同一 Agent 第二个 acquire 失败"""
        asyncio.run(counter.acquire("agent-1"))
        result = asyncio.run(counter.acquire("agent-1"))
        assert result is False

    def test_different_agents_ok(self, counter):
        """不同 Agent 可以同时 acquire"""
        assert asyncio.run(counter.acquire("agent-1")) is True
        assert asyncio.run(counter.acquire("agent-2")) is True

    def test_per_agent_limit_2(self):
        """max_per_agent=2 允许两个"""
        c = ActiveAgentCounter(max_global=5, max_per_agent=2)
        assert asyncio.run(c.acquire("a1")) is True
        assert asyncio.run(c.acquire("a1")) is True
        assert asyncio.run(c.acquire("a1")) is False  # 第三个失败


# ---------------------------------------------------------------------------
# T3: release 恢复
# ---------------------------------------------------------------------------

class TestRelease:
    def test_release_allows_new_acquire(self, counter):
        """release 后可以重新 acquire"""
        asyncio.run(counter.acquire("agent-1"))
        counter.release("agent-1")
        result = asyncio.run(counter.acquire("agent-1"))
        assert result is True

    def test_release_updates_count(self, counter):
        """release 后计数正确"""
        asyncio.run(counter.acquire("agent-1"))
        assert counter.global_active == 1
        counter.release("agent-1")
        assert counter.global_active == 0

    def test_release_same_agent_twice(self, counter):
        """release 后同一 Agent 可以再次 acquire"""
        asyncio.run(counter.acquire("agent-1"))
        counter.release("agent-1")
        assert asyncio.run(counter.can_acquire("agent-1")) is True

    def test_release_updates_active_agents(self, counter):
        """active_agents 正确追踪"""
        asyncio.run(counter.acquire("agent-1"))
        assert counter.active_agents == {"agent-1": 1}
        counter.release("agent-1")
        assert "agent-1" not in counter.active_agents

    def test_full_cycle(self, counter):
        """完整获取-释放循环"""
        # Fill
        for i in range(3):
            asyncio.run(counter.acquire(f"a{i}"))
        assert counter.global_active == 3

        # Full → can't acquire
        assert asyncio.run(counter.can_acquire("extra")) is False

        # Release one
        counter.release("a1")
        assert counter.global_active == 2

        # Now can acquire
        assert asyncio.run(counter.acquire("extra")) is True
        assert counter.global_active == 3


# ---------------------------------------------------------------------------
# T4: 并发竞争（P1）
# ---------------------------------------------------------------------------

class TestConcurrency:
    def test_concurrent_acquire_no_exceed(self):
        """并发 acquire 不超过全局限制"""
        counter = ActiveAgentCounter(max_global=3, max_per_agent=1)
        results = []

        async def try_acquire(agent_id):
            r = await counter.acquire(agent_id)
            results.append((agent_id, r))
            if r:
                await asyncio.sleep(0.01)
                counter.release(agent_id)

        async def run():
            tasks = [try_acquire(f"agent-{i}") for i in range(10)]
            await asyncio.gather(*tasks)

        asyncio.run(run())

        succeeded = sum(1 for _, r in results if r)
        # 不超过 max_global 的倍数（因为 release 后可重用）
        assert succeeded > 0

    def test_no_negative_count(self, counter):
        """release 不会导致负数计数"""
        counter.release("nonexistent")  # 不应崩溃
        assert counter.global_active >= 0


# ===================================================================
# === 以下为 Phase 2 补充测试 ===
# ===================================================================

import time
from unittest.mock import patch


# ---------------------------------------------------------------------------
# TestCooldown: 冷却期控制
# ---------------------------------------------------------------------------

class TestCooldown:
    def test_set_cooldown_default(self):
        """默认 120 秒冷却"""
        counter = ActiveAgentCounter(max_global=3, max_per_session=1,
                                     default_cooldown_seconds=120.0)
        counter.set_cooldown("agent-1")
        assert "agent-1" in counter._cooldown_until
        # 验证 until 约为 now + 120
        until = counter._cooldown_until["agent-1"]
        assert until >= time.time() + 119
        assert until <= time.time() + 121

    def test_set_cooldown_custom(self):
        """自定义冷却时间"""
        counter = ActiveAgentCounter(max_global=3, max_per_session=1,
                                     default_cooldown_seconds=120.0)
        counter.set_cooldown("agent-1", seconds=30.0)
        until = counter._cooldown_until["agent-1"]
        assert until >= time.time() + 29
        assert until <= time.time() + 31

    def test_cooldown_blocks_acquire(self):
        """冷却期内 can_acquire 返回 False"""
        counter = ActiveAgentCounter(max_global=3, max_per_session=1,
                                     default_cooldown_seconds=120.0)
        counter.set_cooldown("agent-1", seconds=300.0)
        result = asyncio.run(counter.can_acquire("agent-1"))
        assert result is False

    def test_cooldown_expires(self):
        """冷却过期后可以 acquire（mock time.time）"""
        counter = ActiveAgentCounter(max_global=3, max_per_session=1,
                                     default_cooldown_seconds=120.0)
        counter.set_cooldown("agent-1", seconds=100.0)
        real_until = counter._cooldown_until["agent-1"]

        # Mock time.time to be past the cooldown
        with patch("src.daemon.counter.time.time", return_value=real_until + 1.0):
            result = asyncio.run(counter.can_acquire("agent-1"))
            assert result is True

    def test_cooldown_expired_auto_cleanup(self):
        """过期后 is_cooling_down 清理条目"""
        counter = ActiveAgentCounter(max_global=3, max_per_session=1,
                                     default_cooldown_seconds=120.0)
        counter.set_cooldown("agent-1", seconds=100.0)
        assert "agent-1" in counter._cooldown_until

        real_until = counter._cooldown_until["agent-1"]
        with patch("src.daemon.counter.time.time", return_value=real_until + 1.0):
            assert counter.is_cooling_down("agent-1") is False

        # 条目应被 pop 掉
        assert "agent-1" not in counter._cooldown_until


# ---------------------------------------------------------------------------
# TestIsNearLimit: 全局接近上限检测
# ---------------------------------------------------------------------------

class TestIsNearLimit:
    def test_not_near_limit(self):
        """空闲时不接近"""
        counter = ActiveAgentCounter(max_global=3, max_per_session=1)
        assert counter.is_near_limit() is False

    def test_near_limit_at_margin(self):
        """活跃数 = max-1 时为 True"""
        counter = ActiveAgentCounter(max_global=3, max_per_session=1)
        # 手动设置全局活跃数
        counter._global_active = 2  # max_global=3, margin=1 → 3-1=2
        assert counter.is_near_limit() is True

    def test_at_limit(self):
        """活跃数 = max 时为 True"""
        counter = ActiveAgentCounter(max_global=3, max_per_session=1)
        counter._global_active = 3
        assert counter.is_near_limit() is True

    def test_custom_margin(self):
        """margin=2 时提前触发"""
        counter = ActiveAgentCounter(max_global=5, max_per_session=1)
        counter._global_active = 3  # max=5, margin=2 → 5-2=3
        assert counter.is_near_limit(margin=2) is True
        # 低于阈值
        counter._global_active = 2
        assert counter.is_near_limit(margin=2) is False


# ---------------------------------------------------------------------------
# TestThreeLayerCheck: 三层检查
# ---------------------------------------------------------------------------

class TestThreeLayerCheck:
    def test_layer1_cooldown_blocks(self):
        """第一层：冷却阻断"""
        counter = ActiveAgentCounter(max_global=5, max_per_session=1,
                                     max_concurrent_sessions=3)
        counter.set_cooldown("agent-1", seconds=300.0)
        # 即使全局空闲，冷却也阻断
        result = asyncio.run(counter.can_acquire("agent-1"))
        assert result is False

    def test_layer2_global_blocks(self):
        """第二层：全局满阻断"""
        counter = ActiveAgentCounter(max_global=2, max_per_session=1,
                                     max_concurrent_sessions=3)
        # 手动填满全局
        counter._global_active = 2
        result = asyncio.run(counter.can_acquire("agent-1"))
        assert result is False

    def test_layer3_per_agent_blocks(self):
        """第三层：per-agent 满阻断"""
        counter = ActiveAgentCounter(max_global=5, max_per_session=1,
                                     max_concurrent_sessions=2)
        # 手动填满 per-agent
        counter._agent_active["agent-1"] = 2
        result = asyncio.run(counter.can_acquire("agent-1"))
        assert result is False

    def test_all_layers_pass(self):
        """三层都通过"""
        counter = ActiveAgentCounter(max_global=5, max_per_session=1,
                                     max_concurrent_sessions=3)
        result = asyncio.run(counter.can_acquire("agent-1"))
        assert result is True
