"""API-key management cannot cross tenants or escalate delegated permissions."""
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4

import pytest
from src.core.exceptions import ForbiddenError, NotFoundError
from src.domain.api_keys.repository import ApiKeyRepository
from src.domain.api_keys.service import ApiKeyService


@pytest.mark.parametrize("operation", ["create", "list", "revoke"])
async def test_non_manager_cannot_manage_keys(operation):
    repository = AsyncMock()
    workspaces = AsyncMock()
    workspaces.check_permission.return_value = False
    service = ApiKeyService(repository, workspaces)
    ws, user = uuid4(), uuid4()
    with pytest.raises(ForbiddenError):
        if operation == "create":
            await service.create(ws, user, "automation", [])
        elif operation == "list":
            await service.list_for_workspace(ws, user)
        else:
            await service.revoke(ws, user, uuid4())
    repository.create.assert_not_awaited()
    repository.list_for_workspace.assert_not_awaited()
    repository.revoke.assert_not_awaited()
    workspaces.check_permission.assert_awaited_once_with(ws, user, "public_api.manage")


async def test_manager_cannot_delegate_billing_permission_they_do_not_hold():
    repository = AsyncMock()
    workspaces = AsyncMock()
    workspaces.check_permission.side_effect = [True, False]
    with pytest.raises(ForbiddenError):
        await ApiKeyService(repository, workspaces).create(
            uuid4(), uuid4(), "automation", ["billing.manage"],
        )
    repository.create.assert_not_awaited()


async def test_creation_binds_workspace_and_creator():
    repository, workspaces = AsyncMock(), AsyncMock()
    workspaces.check_permission.return_value = True
    ws, user = uuid4(), uuid4()
    await ApiKeyService(repository, workspaces).create(ws, user, "automation", ["projects.read"])
    repository.create.assert_awaited_once_with(
        workspace_id=ws, created_by=user, name="automation", permissions=["projects.read"],
    )


async def test_revocation_passes_workspace_and_rejects_missing_or_foreign_key():
    repository, workspaces = AsyncMock(), AsyncMock()
    workspaces.check_permission.return_value = True
    repository.revoke.return_value = False
    ws, user, key = uuid4(), uuid4(), uuid4()
    with pytest.raises(NotFoundError):
        await ApiKeyService(repository, workspaces).revoke(ws, user, key)
    repository.revoke.assert_awaited_once_with(key, ws)


async def test_repository_revocation_queries_tenant_and_preserves_original_timestamp():
    db = AsyncMock()
    result = MagicMock()
    original = object()
    result.scalar_one_or_none.return_value = SimpleNamespace(revoked_at=original)
    db.execute.return_value = result
    key, ws = uuid4(), uuid4()
    assert await ApiKeyRepository(db).revoke(key, ws)
    query = db.execute.call_args.args[0]
    sql = str(query)
    assert "api_keys.workspace_id =" in sql
    assert "api_keys.id =" in sql
    assert "FOR UPDATE" in sql
    assert ws in query.compile().params.values()
    assert key in query.compile().params.values()
    assert result.scalar_one_or_none.return_value.revoked_at is original
    db.flush.assert_not_awaited()


async def test_repository_missing_key_does_not_mutate():
    db = AsyncMock()
    result = MagicMock()
    result.scalar_one_or_none.return_value = None
    db.execute.return_value = result
    assert not await ApiKeyRepository(db).revoke(uuid4(), uuid4())
    db.flush.assert_not_awaited()
