"""Tests for create_tool_approval_request functionality."""

import pytest
from unittest.mock import Mock, patch
from datetime import datetime
import requests

from frisk_sdk.core.tool_approval_request import (
    cancel_many_tool_approval_requests,
    get_or_create_tool_approval_request,
    get_or_create_many_tool_approval_requests,
    ToolApprovalRequest,
    ToolApprovalRequestStatusUpdateResult,
    ToolApprovalStatus,
)
from frisk_sdk.framework_adapter.framework_adapter import NormalizedToolCall
from frisk_sdk.core.tool_call_decision import ToolCallDecision, DecisionOutcome


@pytest.fixture
def mock_token_runtime():
    """Mock AccessTokenProviderRuntime."""
    runtime = Mock()
    runtime.get_access_token.return_value = "test_access_token_123"
    return runtime


@pytest.fixture
def sample_tool_call():
    """Sample NormalizedToolCall for testing."""
    return NormalizedToolCall(
        id="call_abc123",
        name="send_email",
        args={"to": "user@example.com", "subject": "Test"},
    )


@pytest.fixture
def sample_decision_with_policy():
    """Sample ToolCallDecision with policy information."""
    return ToolCallDecision(
        outcome=DecisionOutcome.ESCALATE,
        rules_matched_count=1,
        reason="Requires approval",
        policy_id="policy_123",
        policy_version_id="v1",
    )


@pytest.fixture
def sample_decision_without_policy():
    """Sample ToolCallDecision without policy information."""
    return ToolCallDecision(
        outcome=DecisionOutcome.ESCALATE,
        rules_matched_count=1,
        reason="Requires approval",
        policy_id=None,
        policy_version_id=None,
    )


@pytest.fixture
def sample_api_response():
    """Sample API response data."""
    return {
        "id": "tar_xyz789",
        "toolCallId": "call_abc123",
        "sessionId": "sess_456",
        "policyId": "policy_123",
        "policyVersionId": "v1",
        "toolName": "send_email",
        "agentId": "agent_001",
        "status": "PENDING",
        "organizationId": "org_123",
        "requestedAt": "2026-03-10T12:00:00Z",
        "decidedAt": None,
        "decidedByUserId": None,
        "decisionNotes": None,
    }


@pytest.fixture
def sample_batch_request_inputs(sample_decision_with_policy):
    """Sample batch tool approval request inputs."""
    policy_id = sample_decision_with_policy.policy_id or "unknown"
    policy_version_id = sample_decision_with_policy.policy_version_id or "unknown"

    return [
        {
            "tool_call_id": "call_1",
            "policy_id": policy_id,
            "policy_version_id": policy_version_id,
            "tool_name": "send_email",
        },
        {
            "tool_call_id": "call_2",
            "policy_id": policy_id,
            "policy_version_id": policy_version_id,
            "tool_name": "search_docs",
        },
    ]


class TestToolApprovalRequestFromDict:
    """Tests for ToolApprovalRequest.from_dict method."""

    def test_from_dict_with_all_fields(self, sample_api_response):
        """Test parsing a complete API response."""
        result = ToolApprovalRequest.from_dict(sample_api_response)

        assert result.id == "tar_xyz789"
        assert result.tool_call_id == "call_abc123"
        assert result.session_id == "sess_456"
        assert result.policy_id == "policy_123"
        assert result.policy_version_id == "v1"
        assert result.tool_name == "send_email"
        assert result.agent_id == "agent_001"
        assert result.status == ToolApprovalStatus.PENDING
        assert result.organization_id == "org_123"
        assert isinstance(result.requested_at, datetime)
        assert result.decided_at is None
        assert result.decided_by_user_id is None
        assert result.decision_notes is None

    def test_from_dict_with_decided_fields(self, sample_api_response):
        """Test parsing a response with decision information."""
        sample_api_response["decidedAt"] = "2026-03-10T13:00:00Z"
        sample_api_response["decidedByUserId"] = "user_789"
        sample_api_response["decisionNotes"] = "Approved by manager"

        result = ToolApprovalRequest.from_dict(sample_api_response)

        assert isinstance(result.decided_at, datetime)
        assert result.decided_by_user_id == "user_789"
        assert result.decision_notes == "Approved by manager"

    def test_from_dict_handles_z_timezone(self, sample_api_response):
        """Test that Z timezone suffix is properly handled."""
        result = ToolApprovalRequest.from_dict(sample_api_response)
        # Should not raise an error and should parse correctly
        assert result.requested_at.year == 2026
        assert result.requested_at.month == 3
        assert result.requested_at.day == 10


@pytest.mark.asyncio
class TestCreateToolApprovalRequest:
    """Tests for create_tool_approval_request function."""

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_successful_request_with_policy_ids(
        self,
        mock_post,
        mock_token_runtime,
        sample_tool_call,
        sample_decision_with_policy,
        sample_api_response,
    ):
        """Test successful creation with policy IDs present."""
        mock_response = Mock()
        mock_response.json.return_value = sample_api_response
        mock_response.status_code = 200
        mock_post.return_value = mock_response

        result = await get_or_create_tool_approval_request(
            tool_call_id=sample_tool_call.id,
            tool_name=sample_tool_call.name,
            policy_id=sample_decision_with_policy.policy_id or "unknown",
            policy_version_id=sample_decision_with_policy.policy_version_id
            or "unknown",
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        # Verify the request was made correctly
        mock_post.assert_called_once()
        call_args = mock_post.call_args

        assert call_args.kwargs["json"] == {
            "toolCallId": "call_abc123",
            "policyId": "policy_123",
            "policyVersionId": "v1",
            "sessionId": "sess_456",
            "toolName": "send_email",
        }
        assert call_args.kwargs["headers"] == {
            "Authorization": "Bearer test_access_token_123",
            "Content-Type": "application/json",
        }
        assert call_args.kwargs["timeout"] == 10

        # Verify the result
        assert result is not None
        assert result.id == "tar_xyz789"
        assert result.tool_call_id == "call_abc123"
        assert result.policy_id == "policy_123"

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_request_without_policy_ids_uses_unknown(
        self,
        mock_post,
        mock_token_runtime,
        sample_tool_call,
        sample_decision_without_policy,
        sample_api_response,
    ):
        """Test that missing policy IDs are replaced with 'unknown'."""
        mock_response = Mock()
        sample_api_response["policyId"] = "unknown"
        sample_api_response["policyVersionId"] = "unknown"
        mock_response.json.return_value = sample_api_response
        mock_response.status_code = 200
        mock_post.return_value = mock_response

        result = await get_or_create_tool_approval_request(
            tool_call_id=sample_tool_call.id,
            tool_name=sample_tool_call.name,
            policy_id=sample_decision_without_policy.policy_id or "unknown",
            policy_version_id=sample_decision_without_policy.policy_version_id
            or "unknown",
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        # Verify placeholder values were used
        call_args = mock_post.call_args
        assert call_args.kwargs["json"]["policyId"] == "unknown"
        assert call_args.kwargs["json"]["policyVersionId"] == "unknown"

        # Should still return result
        assert result is not None

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_endpoint_url_construction(
        self,
        mock_post,
        mock_token_runtime,
        sample_tool_call,
        sample_decision_with_policy,
        sample_api_response,
    ):
        """Test that the endpoint URL is constructed correctly."""
        mock_response = Mock()
        mock_response.json.return_value = sample_api_response
        mock_response.status_code = 200
        mock_post.return_value = mock_response

        await get_or_create_tool_approval_request(
            tool_call_id=sample_tool_call.id,
            tool_name=sample_tool_call.name,
            policy_id=sample_decision_with_policy.policy_id or "unknown",
            policy_version_id=sample_decision_with_policy.policy_version_id
            or "unknown",
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        # Check the URL
        call_args = mock_post.call_args
        assert (
            call_args[0][0]
            == "https://api.frisk.ai/api/tool_approval_requests/get_or_create"
        )

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_http_error_returns_none(
        self,
        mock_post,
        mock_token_runtime,
        sample_tool_call,
        sample_decision_with_policy,
    ):
        """Test that HTTP errors are handled gracefully."""
        mock_response = Mock()
        mock_response.status_code = 400
        mock_response.text = "Bad Request"
        mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError()
        mock_post.return_value = mock_response

        result = await get_or_create_tool_approval_request(
            tool_call_id=sample_tool_call.id,
            tool_name=sample_tool_call.name,
            policy_id=sample_decision_with_policy.policy_id or "unknown",
            policy_version_id=sample_decision_with_policy.policy_version_id
            or "unknown",
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result is None

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_network_error_returns_none(
        self,
        mock_post,
        mock_token_runtime,
        sample_tool_call,
        sample_decision_with_policy,
    ):
        """Test that network errors are handled gracefully."""
        mock_post.side_effect = requests.exceptions.ConnectionError("Network error")

        result = await get_or_create_tool_approval_request(
            tool_call_id=sample_tool_call.id,
            tool_name=sample_tool_call.name,
            policy_id=sample_decision_with_policy.policy_id or "unknown",
            policy_version_id=sample_decision_with_policy.policy_version_id
            or "unknown",
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result is None

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_timeout_error_returns_none(
        self,
        mock_post,
        mock_token_runtime,
        sample_tool_call,
        sample_decision_with_policy,
    ):
        """Test that timeout errors are handled gracefully."""
        mock_post.side_effect = requests.exceptions.Timeout("Request timed out")

        result = await get_or_create_tool_approval_request(
            tool_call_id=sample_tool_call.id,
            tool_name=sample_tool_call.name,
            policy_id=sample_decision_with_policy.policy_id or "unknown",
            policy_version_id=sample_decision_with_policy.policy_version_id
            or "unknown",
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result is None

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_invalid_json_response_returns_none(
        self,
        mock_post,
        mock_token_runtime,
        sample_tool_call,
        sample_decision_with_policy,
    ):
        """Test that invalid JSON responses are handled gracefully."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.side_effect = ValueError("Invalid JSON")
        mock_post.return_value = mock_response

        result = await get_or_create_tool_approval_request(
            tool_call_id=sample_tool_call.id,
            tool_name=sample_tool_call.name,
            policy_id=sample_decision_with_policy.policy_id or "unknown",
            policy_version_id=sample_decision_with_policy.policy_version_id
            or "unknown",
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result is None

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_missing_required_fields_returns_none(
        self,
        mock_post,
        mock_token_runtime,
        sample_tool_call,
        sample_decision_with_policy,
    ):
        """Test that responses missing required fields are handled gracefully."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"id": "tar_123"}  # Missing other fields
        mock_post.return_value = mock_response

        result = await get_or_create_tool_approval_request(
            tool_call_id=sample_tool_call.id,
            tool_name=sample_tool_call.name,
            policy_id=sample_decision_with_policy.policy_id or "unknown",
            policy_version_id=sample_decision_with_policy.policy_version_id
            or "unknown",
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result is None

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_uses_access_token_from_runtime(
        self,
        mock_post,
        mock_token_runtime,
        sample_tool_call,
        sample_decision_with_policy,
        sample_api_response,
    ):
        """Test that the access_token is used for authentication."""
        mock_response = Mock()
        mock_response.json.return_value = sample_api_response
        mock_response.status_code = 200
        mock_post.return_value = mock_response

        await get_or_create_tool_approval_request(
            tool_call_id=sample_tool_call.id,
            tool_name=sample_tool_call.name,
            policy_id=sample_decision_with_policy.policy_id or "unknown",
            policy_version_id=sample_decision_with_policy.policy_version_id
            or "unknown",
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="custom_token_xyz",
        )

        call_args = mock_post.call_args
        assert call_args.kwargs["headers"]["Authorization"] == "Bearer custom_token_xyz"

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_correct_payload_structure(
        self,
        mock_post,
        mock_token_runtime,
        sample_tool_call,
        sample_decision_with_policy,
        sample_api_response,
    ):
        """Test that the payload matches the expected structure."""
        mock_response = Mock()
        mock_response.json.return_value = sample_api_response
        mock_response.status_code = 200
        mock_post.return_value = mock_response

        await get_or_create_tool_approval_request(
            tool_call_id=sample_tool_call.id,
            tool_name=sample_tool_call.name,
            policy_id=sample_decision_with_policy.policy_id or "unknown",
            policy_version_id=sample_decision_with_policy.policy_version_id
            or "unknown",
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        # Verify payload structure
        call_args = mock_post.call_args
        payload = call_args.kwargs["json"]

        # Check all required fields are present
        assert "toolCallId" in payload
        assert "policyId" in payload
        assert "policyVersionId" in payload
        assert "sessionId" in payload
        assert "toolName" in payload

        # Check no extra fields
        assert len(payload) == 5

        # Check field types and values
        assert isinstance(payload["toolCallId"], str)
        assert isinstance(payload["policyId"], str)
        assert isinstance(payload["policyVersionId"], str)
        assert isinstance(payload["sessionId"], str)
        assert isinstance(payload["toolName"], str)


@pytest.mark.asyncio
class TestGetOrCreateManyToolApprovalRequests:
    """Tests for get_or_create_many_tool_approval_requests."""

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_batch_success_returns_mapping(
        self, mock_post, sample_batch_request_inputs
    ):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "id": "tar_1",
                "toolCallId": "call_1",
                "sessionId": "sess_456",
                "policyId": "policy_123",
                "policyVersionId": "v1",
                "toolName": "send_email",
                "agentId": "agent_001",
                "status": "PENDING",
                "organizationId": "org_123",
                "requestedAt": "2026-03-10T12:00:00Z",
                "decidedAt": None,
                "decidedByUserId": None,
                "decisionNotes": None,
            },
            {
                "id": "tar_2",
                "toolCallId": "call_2",
                "sessionId": "sess_456",
                "policyId": "policy_123",
                "policyVersionId": "v1",
                "toolName": "search_docs",
                "agentId": "agent_001",
                "status": "PENDING",
                "organizationId": "org_123",
                "requestedAt": "2026-03-10T12:00:00Z",
                "decidedAt": None,
                "decidedByUserId": None,
                "decisionNotes": None,
            },
        ]
        mock_post.return_value = mock_response

        result = await get_or_create_many_tool_approval_requests(
            tool_approval_request_inputs=sample_batch_request_inputs,
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert set(result.keys()) == {"call_1", "call_2"}
        assert result["call_1"] is not None
        assert result["call_1"].id == "tar_1"
        assert result["call_2"] is not None
        assert result["call_2"].id == "tar_2"

        mock_post.assert_called_once()
        call_args = mock_post.call_args
        assert (
            call_args[0][0]
            == "https://api.frisk.ai/api/tool_approval_requests/get_or_create_many"
        )
        assert (
            call_args.kwargs["headers"]["Authorization"]
            == "Bearer test_access_token_123"
        )
        assert call_args.kwargs["json"]["sessionId"] == "sess_456"
        assert len(call_args.kwargs["json"]["inputs"]) == 2

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_batch_partial_response_sets_missing_to_none(
        self, mock_post, sample_batch_request_inputs
    ):
        mock_response = Mock()
        mock_response.status_code = 200
        # Only returns one of the two requested tool calls.
        mock_response.json.return_value = [
            {
                "id": "tar_1",
                "toolCallId": "call_1",
                "sessionId": "sess_456",
                "policyId": "policy_123",
                "policyVersionId": "v1",
                "toolName": "send_email",
                "agentId": "agent_001",
                "status": "PENDING",
                "organizationId": "org_123",
                "requestedAt": "2026-03-10T12:00:00Z",
                "decidedAt": None,
                "decidedByUserId": None,
                "decisionNotes": None,
            }
        ]
        mock_post.return_value = mock_response

        result = await get_or_create_many_tool_approval_requests(
            tool_approval_request_inputs=sample_batch_request_inputs,
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result["call_1"] is not None
        assert result["call_1"].id == "tar_1"
        assert result["call_2"] is None


@pytest.mark.asyncio
class TestCancelManyToolApprovalRequests:
    """Tests for cancel_many_tool_approval_requests."""

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_cancel_many_success_returns_mapping(self, mock_post):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "updated": True,
                "result": {
                    "id": "tar_1",
                    "toolCallId": "call_1",
                    "sessionId": "sess_456",
                    "policyId": "policy_123",
                    "policyVersionId": "v1",
                    "toolName": "send_email",
                    "agentId": "agent_001",
                    "status": "CANCELED",
                    "organizationId": "org_123",
                    "requestedAt": "2026-03-10T12:00:00Z",
                    "decidedAt": "2026-03-10T12:05:00Z",
                    "decidedByUserId": "user_123",
                    "decisionNotes": "Canceled after completion",
                },
            },
            {
                "updated": False,
                "result": {
                    "id": "tar_2",
                    "toolCallId": "call_2",
                    "sessionId": "sess_456",
                    "policyId": "policy_123",
                    "policyVersionId": "v1",
                    "toolName": "search_docs",
                    "agentId": "agent_001",
                    "status": "PENDING",
                    "organizationId": "org_123",
                    "requestedAt": "2026-03-10T12:00:00Z",
                    "decidedAt": None,
                    "decidedByUserId": None,
                    "decisionNotes": None,
                },
            },
        ]
        mock_post.return_value = mock_response

        result = await cancel_many_tool_approval_requests(
            approval_request_ids=["tar_1", "tar_2"],
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert set(result.keys()) == {"tar_1", "tar_2"}
        assert isinstance(result["tar_1"], ToolApprovalRequestStatusUpdateResult)
        assert result["tar_1"] is not None
        assert result["tar_1"].updated is True
        assert result["tar_1"].result is not None
        assert result["tar_1"].result.status == ToolApprovalStatus.CANCELED
        assert result["tar_2"] is not None
        assert result["tar_2"].updated is False
        assert result["tar_2"].result is not None
        assert result["tar_2"].result.status == ToolApprovalStatus.PENDING

        mock_post.assert_called_once()
        call_args = mock_post.call_args
        assert (
            call_args[0][0]
            == "https://api.frisk.ai/api/tool_approval_requests/cancel_many"
        )
        assert call_args.kwargs["json"] == {"ids": ["tar_1", "tar_2"]}
        assert (
            call_args.kwargs["headers"]["Authorization"]
            == "Bearer test_access_token_123"
        )

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_cancel_many_invalid_item_is_skipped(self, mock_post):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "updated": True,
                "result": {
                    "id": "tar_1",
                    "toolCallId": "call_1",
                    "sessionId": "sess_456",
                    "policyId": "policy_123",
                    "policyVersionId": "v1",
                    "toolName": "send_email",
                    "agentId": "agent_001",
                    "status": "CANCELED",
                    "organizationId": "org_123",
                    "requestedAt": "2026-03-10T12:00:00Z",
                    "decidedAt": "2026-03-10T12:05:00Z",
                    "decidedByUserId": "user_123",
                    "decisionNotes": "Canceled after completion",
                },
            },
            "not-a-dict",
        ]
        mock_post.return_value = mock_response

        result = await cancel_many_tool_approval_requests(
            approval_request_ids=["tar_1", "tar_2"],
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result["tar_1"] is not None
        assert result["tar_1"].updated is True
        assert result["tar_2"] is None

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_cancel_many_non_list_response_returns_none_for_all(self, mock_post):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"unexpected": "object"}
        mock_post.return_value = mock_response

        result = await cancel_many_tool_approval_requests(
            approval_request_ids=["tar_1", "tar_2"],
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result == {"tar_1": None, "tar_2": None}

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_cancel_many_network_error_returns_none_for_all(self, mock_post):
        mock_post.side_effect = requests.exceptions.ConnectionError("Network error")

        result = await cancel_many_tool_approval_requests(
            approval_request_ids=["tar_1", "tar_2"],
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result == {"tar_1": None, "tar_2": None}

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_batch_network_error_returns_none_for_all(
        self, mock_post, sample_batch_request_inputs
    ):
        mock_post.side_effect = requests.exceptions.ConnectionError("Network error")

        result = await get_or_create_many_tool_approval_requests(
            tool_approval_request_inputs=sample_batch_request_inputs,
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result == {"call_1": None, "call_2": None}

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_batch_empty_input_returns_empty_without_network_call(
        self, mock_post
    ):
        result = await get_or_create_many_tool_approval_requests(
            tool_approval_request_inputs=[],
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result == {}
        mock_post.assert_not_called()

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_batch_non_list_response_returns_none_for_all(
        self, mock_post, sample_batch_request_inputs
    ):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"unexpected": "object"}
        mock_post.return_value = mock_response

        result = await get_or_create_many_tool_approval_requests(
            tool_approval_request_inputs=sample_batch_request_inputs,
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result == {"call_1": None, "call_2": None}

    @patch("frisk_sdk.core.tool_approval_request.requests.post")
    async def test_batch_invalid_item_is_skipped_but_valid_items_are_kept(
        self, mock_post, sample_batch_request_inputs
    ):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "id": "tar_1",
                "toolCallId": "call_1",
                "sessionId": "sess_456",
                "policyId": "policy_123",
                "policyVersionId": "v1",
                "toolName": "send_email",
                "agentId": "agent_001",
                "status": "PENDING",
                "organizationId": "org_123",
                "requestedAt": "2026-03-10T12:00:00Z",
                "decidedAt": None,
                "decidedByUserId": None,
                "decisionNotes": None,
            },
            {
                "id": "tar_2",
                "toolCallId": "call_2",
                "sessionId": "sess_456",
                "policyId": "policy_123",
                "policyVersionId": "v1",
                "toolName": "search_docs",
                "agentId": "agent_001",
                "status": "NOT_A_REAL_STATUS",
                "organizationId": "org_123",
                "requestedAt": "2026-03-10T12:00:00Z",
                "decidedAt": None,
                "decidedByUserId": None,
                "decisionNotes": None,
            },
        ]
        mock_post.return_value = mock_response

        result = await get_or_create_many_tool_approval_requests(
            tool_approval_request_inputs=sample_batch_request_inputs,
            session_id="sess_456",
            api_base_url="https://api.frisk.ai",
            access_token="test_access_token_123",
        )

        assert result["call_1"] is not None
        assert result["call_1"].id == "tar_1"
        assert result["call_2"] is None
