"""Tool-class rate limiting: fail-open reads, fail-closed mutations, unclassified passthrough."""

from uuid import uuid4

import pytest
from src.core.exceptions import RateLimitError
from src.domain.agent_access.rate_limit import enforce_tool_rate_limit


class FakeRedis:
    def __init__(self):
        self.counts: dict[str, int] = {}

    async def incr(self, key):
        self.counts[key] = self.counts.get(key, 0) + 1
        return self.counts[key]

    async def expire(self, key, ttl):
        return True


class BrokenRedis:
    async def incr(self, key):
        raise ConnectionError("redis unreachable")

    async def expire(self, key, ttl):
        raise ConnectionError("redis unreachable")


async def test_no_op_when_redis_is_unset():
    # Background-worker callers of make_agent_service() never pass a redis client.
    await enforce_tool_rate_limit(
        None, grant_id=uuid4(), workspace_id=uuid4(), tool_name="start_build"
    )


async def test_no_op_for_unclassified_tool_name():
    redis = FakeRedis()
    await enforce_tool_rate_limit(
        redis, grant_id=uuid4(), workspace_id=uuid4(), tool_name="not_a_real_tool"
    )
    assert redis.counts == {}


async def test_grant_limit_trips_before_workspace_limit():
    redis = FakeRedis()
    grant_id, workspace_id = uuid4(), uuid4()
    for _ in range(120):
        await enforce_tool_rate_limit(
            redis, grant_id=grant_id, workspace_id=workspace_id, tool_name="list_projects"
        )
    with pytest.raises(RateLimitError):
        await enforce_tool_rate_limit(
            redis, grant_id=grant_id, workspace_id=workspace_id, tool_name="list_projects"
        )


async def test_workspace_aggregate_limits_multiple_grants():
    """Several grants in one workspace cannot add up to an unbounded aggregate."""
    redis = FakeRedis()
    workspace_id = uuid4()
    grants = [uuid4() for _ in range(10)]
    successes = 0
    with pytest.raises(RateLimitError):
        for grant_id in grants:
            for _ in range(100):
                await enforce_tool_rate_limit(
                    redis, grant_id=grant_id, workspace_id=workspace_id, tool_name="list_projects"
                )
                successes += 1
    assert successes == 600  # workspace read.light limit is 600/min; call 601 trips it


async def test_read_class_fails_open_when_redis_errors():
    # Must not raise: a Redis hiccup should not take down inspection tools.
    await enforce_tool_rate_limit(
        BrokenRedis(), grant_id=uuid4(), workspace_id=uuid4(), tool_name="list_projects"
    )


@pytest.mark.parametrize("tool_name", ["start_build", "purchase_credits"])
async def test_high_impact_classes_fail_closed_when_redis_errors(tool_name):
    with pytest.raises(RateLimitError):
        await enforce_tool_rate_limit(
            BrokenRedis(), grant_id=uuid4(), workspace_id=uuid4(), tool_name=tool_name
        )
