"""Tests for CallTransferManager Redis-backed transfer-context lookup.

These tests verify (regression for issue #328):
1. Lookup by original_call_sid resolves via a secondary index, never an
   O(N) `KEYS transfer:context:*` keyspace scan.
2. A lookup for an unknown call sid returns None without scanning.
3. Removing a transfer context also clears its call-sid index entry.
"""

from typing import Dict, List

import pytest


class _FakeRedis:
    """Minimal in-memory async Redis double.

    Counts calls to ``keys()`` so tests can assert the lookup path no longer
    performs an O(N) keyspace scan (the regression behind issue #328).
    """

    def __init__(self) -> None:
        self._store: Dict[str, str] = {}
        self.keys_call_count = 0

    async def setex(self, key: str, ttl: int, value: str) -> None:
        self._store[key] = value

    async def set(
        self, key: str, value: str, *, ex: int | None = None, nx: bool = False
    ):
        if nx and key in self._store:
            return None
        self._store[key] = value
        return True

    async def get(self, key: str):
        return self._store.get(key)

    async def delete(self, *keys: str) -> None:
        for key in keys:
            self._store.pop(key, None)

    async def keys(self, pattern: str) -> List[str]:
        self.keys_call_count += 1
        if pattern.endswith("*"):
            prefix = pattern[:-1]
            return [k for k in self._store if k.startswith(prefix)]
        return [k for k in self._store if k == pattern]


def _build_context(transfer_id: str, original_call_sid: str):
    from api.services.telephony.transfer_event_protocol import TransferContext

    return TransferContext(
        transfer_id=transfer_id,
        call_sid="dest-call-sid",
        target_number="+15551230000",
        tool_uuid="tool-uuid",
        original_call_sid=original_call_sid,
        conference_name="conference-name",
        initiated_at=0.0,
    )


class TestFindTransferContextByCallSid:
    """Lookup must use the call-sid index, not a KEYS scan (issue #328)."""

    @pytest.mark.asyncio
    async def test_lookup_uses_index_and_not_keys_scan(self):
        from api.services.telephony.call_transfer_manager import CallTransferManager

        fake = _FakeRedis()
        manager = CallTransferManager(redis_client=fake)

        await manager.store_transfer_context(_build_context("tx-1", "caller-abc"))

        found = await manager.find_transfer_context_for_call("caller-abc")

        assert found is not None
        assert found.transfer_id == "tx-1"
        # Regression (issue #328): the lookup must resolve via the secondary
        # index, never an O(N) `KEYS transfer:context:*` keyspace scan.
        assert fake.keys_call_count == 0

    @pytest.mark.asyncio
    async def test_lookup_returns_none_for_unknown_call_sid(self):
        from api.services.telephony.call_transfer_manager import CallTransferManager

        fake = _FakeRedis()
        manager = CallTransferManager(redis_client=fake)

        await manager.store_transfer_context(_build_context("tx-1", "caller-abc"))

        found = await manager.find_transfer_context_for_call("not-a-caller")

        assert found is None
        assert fake.keys_call_count == 0

    @pytest.mark.asyncio
    async def test_remove_clears_call_sid_index(self):
        from api.services.telephony.call_transfer_manager import CallTransferManager

        fake = _FakeRedis()
        manager = CallTransferManager(redis_client=fake)

        await manager.store_transfer_context(_build_context("tx-1", "caller-abc"))
        await manager.remove_transfer_context("tx-1")

        found = await manager.find_transfer_context_for_call("caller-abc")

        assert found is None
        assert fake.keys_call_count == 0


@pytest.mark.asyncio
async def test_claim_transfer_step_is_atomic_and_idempotent():
    from api.services.telephony.call_transfer_manager import CallTransferManager

    manager = CallTransferManager(redis_client=_FakeRedis())

    assert await manager.claim_transfer_step("tx-1", "bridge_requested") is True
    assert await manager.claim_transfer_step("tx-1", "bridge_requested") is False
    assert await manager.claim_transfer_step("tx-1", "aleg_joined") is True
