"""The /tools endpoint emits a structured access-trail event for every call,
read or write, without ever logging tool arguments or returned data."""

from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
from uuid import uuid4

import pytest
import structlog.testing
from src.core.exceptions import ForbiddenError
from src.domain.agent_access.router import execute_tool
from src.domain.agent_access.schemas import ToolCall
from src.domain.agent_access.tools import AgentToolService


def _principal():
    return SimpleNamespace(id=uuid4(), workspace_id=uuid4(), scopes=["projects.read"])


async def test_successful_read_is_audited_with_target_fields_only():
    project_id = str(uuid4())
    service = AsyncMock()
    with structlog.testing.capture_logs() as logs:
        with patch.object(
            AgentToolService, "execute", AsyncMock(return_value={"id": project_id, "name": "Site"})
        ):
            result = await execute_tool(
                ToolCall(name="get_project", arguments={"project_id": project_id}),
                principal=_principal(),
                service=service,
            )
    assert result["id"] == project_id
    [event] = [log for log in logs if log["event"] == "agent_tool.executed"]
    assert event["tool_name"] == "get_project"
    assert event["operation_class"] == "read.light"
    assert event["status"] == "success"
    assert event["project_id"] == project_id
    # The returned project name (arbitrary tool output) must never be logged.
    assert "name" not in event
    assert "Site" not in str(event)


async def test_denied_call_is_audited_without_leaking_the_prompt():
    secret_prompt = "build a site that only I would type, never log verbatim"
    service = AsyncMock()
    with structlog.testing.capture_logs() as logs:
        with patch.object(AgentToolService, "execute", AsyncMock(side_effect=ForbiddenError())):
            with pytest.raises(ForbiddenError):
                await execute_tool(
                    ToolCall(
                        name="start_build",
                        arguments={
                            "request_key": "k" * 8,
                            "project_id": str(uuid4()),
                            "prompt": secret_prompt,
                        },
                    ),
                    principal=_principal(),
                    service=service,
                )
    [event] = [log for log in logs if log["event"] == "agent_tool.executed"]
    assert event["status"] == "denied"
    assert event["error_type"] == "ForbiddenError"
    assert event["operation_class"] == "write.standard"
    assert secret_prompt not in str(event)


async def test_get_build_exposes_summary_and_sanitized_required_input_metadata_only():
    project_id = uuid4()
    run_id = uuid4()
    access = SimpleNamespace(
        redis=None,
        settings=SimpleNamespace(app_url="https://dashboard.p2hs.com"),
        require_scope=AsyncMock(return_value=uuid4()),
    )
    run = SimpleNamespace(
        id=run_id,
        project_id=project_id,
        status="user_input_required",
        revision_id=None,
        result_summary="Configure the Calendly URL; token: 'super-secret-value'",
        pending_user_inputs={
            "version": 1,
            "items": [
                {
                    "id": "CALENDLY_URL",
                    "label": "Calendly URL",
                    "description": "The owner scheduling URL",
                    "type": "public",
                    "value": "https://private.example.com/should-never-leak",
                    "placeholder": "https://example.com",
                },
                {
                    "id": "ERP_TOKEN",
                    "label": "ERP token",
                    "description": "Private API token",
                    "type": "secret",
                    "value": "secret-value",
                },
            ],
        },
    )
    repository = SimpleNamespace(visible_run=AsyncMock(return_value=run))
    result = await AgentToolService(access, repository).execute(
        SimpleNamespace(
            id=uuid4(), workspace_id=uuid4(), scopes=["projects.read"]
        ),
        "get_build",
        {"project_id": str(project_id), "run_id": str(run_id)},
    )
    assert result["status"] == "user_input_required"
    assert result["result_summary"] == "Configure the Calendly URL; token: '<P2HS_SECRET_REDACTED>'"
    assert result["required_user_inputs"] == {
        "version": 1,
        "items": [
            {
                "id": "CALENDLY_URL",
                "label": "Calendly URL",
                "description": "The owner scheduling URL",
                "type": "public",
            },
            {
                "id": "ERP_TOKEN",
                "label": "ERP token",
                "description": "Private API token",
                "type": "secret",
            },
        ],
    }
    assert "value" not in str(result)
    assert "private.example.com" not in str(result)


async def _get_build_result(**run_fields):
    project_id, run_id = uuid4(), uuid4()
    access = SimpleNamespace(
        redis=None,
        settings=SimpleNamespace(app_url="https://dashboard.p2hs.com"),
        require_scope=AsyncMock(return_value=uuid4()),
    )
    run = SimpleNamespace(
        id=run_id, project_id=project_id, revision_id=None, result_summary=None,
        pending_user_inputs=None, **run_fields,
    )
    repository = SimpleNamespace(visible_run=AsyncMock(return_value=run))
    return await AgentToolService(access, repository).execute(
        SimpleNamespace(id=uuid4(), scopes=["projects.read"]),
        "get_build",
        {"project_id": str(project_id), "run_id": str(run_id)},
    )


async def test_get_build_explains_a_failed_run_without_leaking_secrets():
    result = await _get_build_result(
        status="failed",
        error_message="P2HS hit an internal error while provisioning capabilities "
        "(AttributeError). token: 'super-secret-value'",
    )
    assert "AttributeError" in result["failure_message"]
    assert "super-secret-value" not in result["failure_message"]


async def test_get_build_failure_message_is_capped_and_only_for_unfinished_runs():
    long = await _get_build_result(status="blocked", error_message="x" * 5000)
    assert len(long["failure_message"]) == 600
    ok = await _get_build_result(status="completed", error_message="stale text from a retry")
    assert ok["failure_message"] is None
