"""Tests for the healing lock module."""

import pytest

from autodebug.healing.locks import HealingLock, LockInfo


class TestHealingLock:
    """Test distributed healing lock."""

    @pytest.mark.asyncio
    async def test_acquire_succeeds(self):
        """Lock acquisition should succeed when no lock exists."""
        acquired = []

        async def mock_acquire(org_id, test_name, ttl):
            acquired.append((org_id, test_name))
            return True

        async def mock_release(org_id, test_name):
            pass

        lock = HealingLock(db_acquire=mock_acquire, db_release=mock_release)
        result = await lock.acquire("org_1", "test_example")
        assert result is True
        assert len(acquired) == 1

    @pytest.mark.asyncio
    async def test_acquire_fails_when_locked(self):
        """Lock acquisition should fail when lock already held."""
        async def mock_acquire(org_id, test_name, ttl):
            return False  # Already locked

        async def mock_release(org_id, test_name):
            pass

        lock = HealingLock(db_acquire=mock_acquire, db_release=mock_release)
        result = await lock.acquire("org_1", "test_example")
        assert result is False

    @pytest.mark.asyncio
    async def test_release(self):
        """Lock release should call the DB release function."""
        released = []

        async def mock_acquire(org_id, test_name, ttl):
            return True

        async def mock_release(org_id, test_name):
            released.append((org_id, test_name))

        lock = HealingLock(db_acquire=mock_acquire, db_release=mock_release)
        await lock.acquire("org_1", "test_example")
        await lock.release("org_1", "test_example")
        assert len(released) == 1
        assert released[0] == ("org_1", "test_example")

    @pytest.mark.asyncio
    async def test_concurrent_acquire_one_wins(self):
        """Only one of two concurrent acquires should succeed."""
        lock_held = {"held": False}

        async def mock_acquire(org_id, test_name, ttl):
            if lock_held["held"]:
                return False
            lock_held["held"] = True
            return True

        async def mock_release(org_id, test_name):
            lock_held["held"] = False

        lock = HealingLock(db_acquire=mock_acquire, db_release=mock_release)

        # First acquire succeeds
        result1 = await lock.acquire("org_1", "test_example")
        assert result1 is True

        # Second acquire fails
        result2 = await lock.acquire("org_1", "test_example")
        assert result2 is False

        # After release, acquire succeeds again
        await lock.release("org_1", "test_example")
        result3 = await lock.acquire("org_1", "test_example")
        assert result3 is True

    @pytest.mark.asyncio
    async def test_different_tests_independent(self):
        """Locks for different tests should be independent."""
        locks = set()

        async def mock_acquire(org_id, test_name, ttl):
            key = f"{org_id}:{test_name}"
            if key in locks:
                return False
            locks.add(key)
            return True

        async def mock_release(org_id, test_name):
            locks.discard(f"{org_id}:{test_name}")

        lock = HealingLock(db_acquire=mock_acquire, db_release=mock_release)

        # Both should succeed since they're different tests
        result1 = await lock.acquire("org_1", "test_a")
        result2 = await lock.acquire("org_1", "test_b")

        assert result1 is True
        assert result2 is True


class TestLockInfo:
    """Test LockInfo dataclass."""

    def test_not_expired(self):
        import time
        info = LockInfo(
            org_id="org_1",
            test_name="test_a",
            acquired_at=time.time(),
            ttl_seconds=600,
        )
        assert info.is_expired is False

    def test_expired(self):
        import time
        info = LockInfo(
            org_id="org_1",
            test_name="test_a",
            acquired_at=time.time() - 700,
            ttl_seconds=600,
        )
        assert info.is_expired is True
