"""
Integration Tests for Builtin Tools with Control Protocol (Tier 2)

Tests all 12 builtin tools with REAL Control Protocol using MockTransport.
Verifies approval workflows work correctly with actual protocol logic for different danger levels.

Test Coverage:
    - HIGH danger tools: bash_command, delete_file, http_delete (require approval)
    - MEDIUM danger tools: write_file, http_post, http_put (require approval)
    - LOW danger tools: read_file, http_get (auto-approved in single operations)
    - SAFE tools: list_directory, file_exists (no approval needed)
    - Approval outcomes: approved, denied, timeout
    - Batch execution with mixed danger levels
    - Real file operations with tempfile (NO MOCKING)

Uses MockTransport (simpler than InMemoryTransport) for reliable testing.
All file operations use real filesystem with proper cleanup.
"""

import tempfile
from pathlib import Path

import pytest
import pytest_asyncio
from kaizen.core.autonomy.control.protocol import ControlProtocol, ControlResponse
from kaizen.core.autonomy.control.types import ControlRequest
from kaizen.tools import ToolExecutor, ToolRegistry
from kaizen.tools.builtin import register_builtin_tools

from tests.utils.mock_transport import MockTransport


def create_approval_responder(
    transport: MockTransport, approved: bool, reason: str = ""
):
    """
    Create a responder function that will respond to approval requests.

    Args:
        transport: MockTransport instance
        approved: Whether to approve or deny
        reason: Optional reason text

    Returns:
        Async function that responds to requests
    """

    async def responder():
        """Wait for and respond to approval requests."""
        import anyio

        # Wait for request to be written
        for _ in range(50):  # Max 5 seconds wait (50 * 0.1s)
            await anyio.sleep(0.1)
            if transport.written_messages:
                break

        if not transport.written_messages:
            return  # No request written

        # Get last written request
        last_request_json = transport.written_messages[-1]
        request = ControlRequest.from_json(last_request_json)

        # Create matching response
        response = ControlResponse(
            request_id=request.request_id,
            data={"approved": approved, "reason": reason},
        )

        # Queue response
        transport.queue_message(response.to_json())

    return responder


@pytest.fixture
def registry():
    """Create registry with all builtin tools."""
    reg = ToolRegistry()
    register_builtin_tools(reg)
    return reg


@pytest_asyncio.fixture
async def protocol_and_transport():
    """Create Control Protocol with MockTransport."""
    transport = MockTransport()
    await transport.connect()

    protocol = ControlProtocol(transport)

    yield protocol, transport

    # Cleanup
    await protocol.stop()
    await transport.close()


@pytest.fixture
def temp_dir():
    """Create temporary directory for file operations."""
    with tempfile.TemporaryDirectory() as tmpdir:
        yield tmpdir


# ============================================
# HIGH Danger Tools (require approval)
# ============================================


@pytest.mark.asyncio
async def test_bash_command_with_approval(registry, protocol_and_transport):
    """Test HIGH danger bash_command tool with user approval."""
    import anyio

    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol)

    # Start protocol and run test
    async with anyio.create_task_group() as tg:
        await protocol.start(tg)

        # Create responder
        responder = create_approval_responder(
            transport, approved=True, reason="Approved bash command"
        )
        tg.start_soon(responder)

        # Execute HIGH danger bash command
        result = await executor.execute("bash_command", {"command": "echo 'test'"})

        # Stop protocol
        await protocol.stop()

    assert result.success is True
    assert result.approved is True
    assert result.result["success"] is True
    assert "test" in result.result["stdout"]
    assert result.result["exit_code"] == 0


@pytest.mark.asyncio
async def test_delete_file_with_approval(registry, protocol_and_transport, temp_dir):
    """Test HIGH danger delete_file tool with user approval."""
    import anyio

    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol)

    # Create test file
    test_file = Path(temp_dir) / "test_delete.txt"
    test_file.write_text("content to delete")
    assert test_file.exists()

    # Start protocol and run test
    async with anyio.create_task_group() as tg:
        await protocol.start(tg)

        # Create responder
        responder = create_approval_responder(
            transport, approved=True, reason="Approved file deletion"
        )
        tg.start_soon(responder)

        # Execute HIGH danger delete_file
        result = await executor.execute("delete_file", {"path": str(test_file)})

        # Stop protocol
        await protocol.stop()

    assert result.success is True
    assert result.approved is True
    assert result.result["deleted"] is True
    assert result.result["existed"] is True
    assert not test_file.exists()  # Verify file was actually deleted


@pytest.mark.asyncio
async def test_delete_file_denied(registry, protocol_and_transport, temp_dir):
    """Test HIGH danger delete_file denied by user."""
    import anyio

    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol)

    # Create test file
    test_file = Path(temp_dir) / "test_protected.txt"
    test_file.write_text("protected content")
    assert test_file.exists()

    # Start protocol and run test
    async with anyio.create_task_group() as tg:
        await protocol.start(tg)

        # Create responder (DENY)
        responder = create_approval_responder(
            transport, approved=False, reason="User denied deletion"
        )
        tg.start_soon(responder)

        # Execute HIGH danger delete_file (will be denied)
        result = await executor.execute("delete_file", {"path": str(test_file)})

        # Stop protocol
        await protocol.stop()

    assert result.success is False
    assert result.approved is False
    assert "denied" in result.error.lower() or "approval" in result.error.lower()
    assert result.result is None
    assert test_file.exists()  # Verify file was NOT deleted


@pytest.mark.asyncio
async def test_http_delete_with_approval(registry, protocol_and_transport):
    """Test HIGH danger http_delete tool with user approval."""
    import anyio

    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol)

    # Start protocol and run test
    async with anyio.create_task_group() as tg:
        await protocol.start(tg)

        # Create responder
        responder = create_approval_responder(
            transport, approved=True, reason="Approved HTTP DELETE"
        )
        tg.start_soon(responder)

        # Execute HIGH danger http_delete
        # Note: This will fail to connect, but we're testing approval flow
        result = await executor.execute(
            "http_delete", {"url": "http://localhost:9999/api/resource"}
        )

        # Stop protocol
        await protocol.stop()

    # Approval succeeded, execution attempted (connection will fail but that's OK)
    assert result.approved is True
    # Success may be False due to connection error, but approval worked
    assert result.result is not None or result.error is not None


# ============================================
# MEDIUM Danger Tools (require approval)
# ============================================


@pytest.mark.asyncio
async def test_write_file_with_approval(registry, protocol_and_transport, temp_dir):
    """Test MEDIUM danger write_file tool with user approval."""
    import anyio

    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol)

    test_file = Path(temp_dir) / "test_write.txt"

    # Start protocol and run test
    async with anyio.create_task_group() as tg:
        await protocol.start(tg)

        # Create responder
        responder = create_approval_responder(
            transport, approved=True, reason="Approved file write"
        )
        tg.start_soon(responder)

        # Execute MEDIUM danger write_file
        result = await executor.execute(
            "write_file", {"path": str(test_file), "content": "test content"}
        )

        # Stop protocol
        await protocol.stop()

    assert result.success is True
    assert result.approved is True
    assert result.result["written"] is True
    assert test_file.exists()
    assert test_file.read_text() == "test content"


# ============================================
# SAFE Tools (no approval needed)
# ============================================


@pytest.mark.asyncio
async def test_safe_tools_no_approval_needed(
    registry, protocol_and_transport, temp_dir
):
    """Test SAFE tools execute without approval (list_directory, file_exists)."""
    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol)

    # Create test file
    test_file = Path(temp_dir) / "test_safe.txt"
    test_file.write_text("safe content")

    # Test list_directory (SAFE)
    result_list = await executor.execute("list_directory", {"path": temp_dir})

    assert result_list.success is True
    assert result_list.approved is True  # Auto-approved (SAFE)
    assert "test_safe.txt" in result_list.result["files"]
    assert len(transport.written_messages) == 0  # No approval request sent

    # Test file_exists (SAFE)
    result_exists = await executor.execute("file_exists", {"path": str(test_file)})

    assert result_exists.success is True
    assert result_exists.approved is True  # Auto-approved (SAFE)
    assert result_exists.result["exists"] is True
    assert result_exists.result["is_file"] is True
    assert len(transport.written_messages) == 0  # Still no approval requests


# ============================================
# Batch Execution with Mixed Danger Levels
# ============================================


@pytest.mark.asyncio
async def test_batch_mixed_danger_levels(registry, protocol_and_transport, temp_dir):
    """Test batch execution with SAFE + MEDIUM + HIGH tools."""
    import anyio

    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol)

    # Create test files
    read_file = Path(temp_dir) / "read_test.txt"
    read_file.write_text("content to read")

    write_file = Path(temp_dir) / "write_test.txt"
    delete_file = Path(temp_dir) / "delete_test.txt"
    delete_file.write_text("content to delete")

    # Create a multi-request responder that handles 2 sequential approvals
    async def multi_responder():
        """Respond to multiple approval requests in order."""
        for i in range(2):  # 2 approvals needed (MEDIUM + HIGH)
            # Wait for request
            for _ in range(50):
                await anyio.sleep(0.1)
                if len(transport.written_messages) > i:
                    break

            if len(transport.written_messages) <= i:
                continue

            # Get the i-th request
            request_json = transport.written_messages[i]
            request = ControlRequest.from_json(request_json)

            # Create approval response
            response = ControlResponse(
                request_id=request.request_id,
                data={"approved": True, "reason": f"Approved {i+1}"},
            )

            # Queue response
            transport.queue_message(response.to_json())

    # Start protocol and run test
    async with anyio.create_task_group() as tg:
        await protocol.start(tg)

        # Start multi-responder
        tg.start_soon(multi_responder)

        executions = [
            {
                "tool_name": "list_directory",
                "params": {"path": temp_dir},
            },  # SAFE (no approval)
            {
                "tool_name": "file_exists",
                "params": {"path": str(read_file)},
            },  # SAFE (no approval)
            {
                "tool_name": "write_file",
                "params": {"path": str(write_file), "content": "batch write"},
            },  # MEDIUM (approval 1)
            {
                "tool_name": "delete_file",
                "params": {"path": str(delete_file)},
            },  # HIGH (approval 2)
        ]

        results = await executor.execute_batch(executions)

        # Stop protocol
        await protocol.stop()

    assert len(results) == 4

    # SAFE tool (list_directory)
    assert results[0].success is True
    assert results[0].approved is True

    # SAFE tool (file_exists)
    assert results[1].success is True
    assert results[1].approved is True

    # MEDIUM tool (write_file - approved)
    assert results[2].success is True
    assert results[2].approved is True
    assert write_file.exists()

    # HIGH tool (delete_file - approved)
    assert results[3].success is True
    assert results[3].approved is True
    assert not delete_file.exists()


# ============================================
# Approval Timeout
# ============================================


@pytest.mark.asyncio
async def test_approval_timeout_dangerous_tool(
    registry, protocol_and_transport, temp_dir
):
    """Test approval request timeout when no response is provided."""
    import anyio

    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol, timeout=0.5)

    test_file = Path(temp_dir) / "timeout_test.txt"
    test_file.write_text("content")

    # Start protocol and run test (no responder - will timeout)
    async with anyio.create_task_group() as tg:
        await protocol.start(tg)

        # Don't create any responder - will timeout

        # Execute HIGH danger tool (will timeout)
        result = await executor.execute("delete_file", {"path": str(test_file)})

        # Stop protocol
        await protocol.stop()

    assert result.success is False
    assert result.approved is False
    assert "no response received" in result.error.lower()
    assert test_file.exists()  # File should still exist (operation not executed)


# ============================================
# Real File Operations Testing
# ============================================


@pytest.mark.asyncio
async def test_read_file_tool(registry, protocol_and_transport, temp_dir):
    """Test read_file tool with real file operations."""
    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol)

    # Create test file
    test_file = Path(temp_dir) / "read_test.txt"
    test_content = "Line 1\nLine 2\nLine 3"
    test_file.write_text(test_content)

    # Read file (LOW danger - auto-approved in single operations)
    result = await executor.execute("read_file", {"path": str(test_file)})

    assert result.success is True
    assert result.approved is True
    assert result.result["content"] == test_content
    assert result.result["size"] == len(test_content.encode("utf-8"))
    assert result.result["exists"] is True


@pytest.mark.asyncio
async def test_file_operations_full_lifecycle(
    registry, protocol_and_transport, temp_dir
):
    """Test complete file lifecycle: write -> read -> exists -> delete."""
    import anyio

    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol)

    test_file = Path(temp_dir) / "lifecycle_test.txt"

    # Create a multi-request responder that handles 2 approvals (write + delete)
    async def multi_responder():
        """Respond to multiple approval requests in order."""
        for i in range(2):  # 2 approvals needed (MEDIUM write + HIGH delete)
            # Wait for request
            for _ in range(50):
                await anyio.sleep(0.1)
                if len(transport.written_messages) > i:
                    break

            if len(transport.written_messages) <= i:
                continue

            # Get the i-th request
            request_json = transport.written_messages[i]
            request = ControlRequest.from_json(request_json)

            # Create approval response
            response = ControlResponse(
                request_id=request.request_id,
                data={"approved": True, "reason": f"Lifecycle step {i+1}"},
            )

            # Queue response
            transport.queue_message(response.to_json())

    # Start protocol and run test
    async with anyio.create_task_group() as tg:
        await protocol.start(tg)

        # Start multi-responder
        tg.start_soon(multi_responder)

        # Step 1: Write file (MEDIUM - requires approval)
        result_write = await executor.execute(
            "write_file", {"path": str(test_file), "content": "lifecycle content"}
        )

        assert result_write.success is True
        assert result_write.approved is True
        assert test_file.exists()

        # Step 2: Read file (LOW - auto-approved)
        result_read = await executor.execute("read_file", {"path": str(test_file)})

        assert result_read.success is True
        assert result_read.result["content"] == "lifecycle content"

        # Step 3: Check existence (SAFE - no approval)
        result_exists = await executor.execute("file_exists", {"path": str(test_file)})

        assert result_exists.success is True
        assert result_exists.result["exists"] is True

        # Step 4: Delete file (HIGH - requires approval)
        result_delete = await executor.execute("delete_file", {"path": str(test_file)})

        assert result_delete.success is True
        assert result_delete.approved is True
        assert not test_file.exists()

        # Stop protocol
        await protocol.stop()


# ============================================
# Custom Approval Messages
# ============================================


@pytest.mark.asyncio
async def test_custom_approval_messages(registry, protocol_and_transport, temp_dir):
    """Test tools with custom approval message templates."""
    import anyio

    protocol, transport = protocol_and_transport
    executor = ToolExecutor(registry=registry, control_protocol=protocol)

    test_file = Path(temp_dir) / "custom_msg.txt"

    # Start protocol and run test
    async with anyio.create_task_group() as tg:
        await protocol.start(tg)

        # Create responder
        responder = create_approval_responder(
            transport, approved=True, reason="Approved"
        )
        tg.start_soon(responder)

        # Execute tool with custom approval message
        result = await executor.execute(
            "write_file", {"path": str(test_file), "content": "test"}
        )

        # Stop protocol
        await protocol.stop()

    assert result.success is True

    # Verify custom message was used (check written messages)
    assert len(transport.written_messages) > 0
    last_request_json = transport.written_messages[-1]
    request = ControlRequest.from_json(last_request_json)

    # Check approval message contains custom template
    assert f"Write to file: {test_file}" in request.data.get("message", "")
