# #region TestAgentChat.Tools [C:3] [TYPE Module] [SEMANTICS test,agent,tools,langchain]
# @BRIEF Tests for LangChain @tool functions — dual-identity auth, HTTP calls, tool wrapping.
# @RELATION BINDS_TO -> [AgentChat.Tools]
# @TEST_EDGE: tool_rest_call -> tool calls FastAPI with dual-identity headers
# @TEST_EDGE: tool_http_failure -> tool returns error JSON gracefully
# @TEST_EDGE: get_all_tools -> returns expected tool list
import os
from pathlib import Path
import sys
from unittest.mock import AsyncMock, patch

sys.path.append(str(Path(__file__).parent.parent.parent / "src"))

import pytest

os.environ["JWT_SECRET"] = "test-jwt-secret-key"
os.environ["FASTAPI_URL"] = "http://test-backend:8000"
os.environ["SERVICE_JWT"] = "test-service-jwt"
os.environ["OPENAI_API_KEY"] = "sk-test-key"


# #region TestAgentChat.Tools.DualAuth [C:2] [TYPE Function] [SEMANTICS test,tools,auth]
# @BRIEF Dual-identity auth headers built from ContextVar and env vars.

@pytest.mark.asyncio
async def test_tool_dual_auth_headers():
    """Tools should build auth headers from ContextVar when set."""
    from src.agent.context import set_service_jwt, set_user_jwt
    from src.agent.tools import search_dashboards

    # Set JWTs in context
    set_user_jwt("user-jwt-token")
    set_service_jwt("service-jwt-token")

    with patch("httpx.AsyncClient") as mock_client:
        mock_instance = AsyncMock()
        mock_client.return_value.__aenter__.return_value = mock_instance
        mock_instance.get.return_value.text = '{"dashboards": []}'

        await search_dashboards.ainvoke({"query": "test"})

        # Verify the HTTP request included dual-identity headers
        call_kwargs = mock_instance.get.call_args
        assert call_kwargs is not None, "HTTP GET should have been called"
        _, kwargs = call_kwargs
        headers = kwargs.get("headers", {})
        assert "Authorization" in headers, "Should include Authorization header (service JWT)"
        assert headers["Authorization"] == "Bearer service-jwt-token"
        assert "X-User-JWT" in headers, "Should include X-User-JWT header (user JWT)"
        assert headers["X-User-JWT"] == "user-jwt-token"
# #endregion TestAgentChat.Tools.DualAuth


# #region TestAgentChat.Tools.FallbackAuth [C:2] [TYPE Function] [SEMANTICS test,tools,auth,fallback]
# @BRIEF Dual-identity auth falls back to env var when ContextVar is not set.

@pytest.mark.asyncio
async def test_tool_auth_fallback_to_env():
    """Tools should fall back to SERVICE_JWT env var when ContextVar is empty."""
    from src.agent.context import set_service_jwt, set_user_jwt
    import src.agent.tools as tools_mod
    from src.agent.tools import search_dashboards

    # Clear ContextVars
    set_user_jwt("")
    set_service_jwt("")
    os.environ["SERVICE_JWT"] = "env-service-token"

    with patch.object(tools_mod, "FASTAPI_URL", "http://test-backend:8000"), \
         patch("httpx.AsyncClient") as mock_client:
        mock_instance = AsyncMock()
        mock_client.return_value.__aenter__.return_value = mock_instance
        mock_instance.get.return_value.text = '{"dashboards": []}'

        # Since tool uses os.getenv at call time, the env var will be read
        await search_dashboards.ainvoke({"query": "test"})

        call_kwargs = mock_instance.get.call_args
        assert call_kwargs is not None
        _, kwargs = call_kwargs
        headers = kwargs.get("headers", {})
        # Should use env var
        assert "Authorization" in headers
# #endregion TestAgentChat.Tools.FallbackAuth


# #region TestAgentChat.Tools.HttpFailure [C:2] [TYPE Function] [SEMANTICS test,tools,failure]
# @BRIEF Tool handles HTTP failure gracefully (returns error text, not exception).

@pytest.mark.asyncio
async def test_tool_http_exception_handling():
    """Tool should propagate HTTP exception as error text."""
    from src.agent.context import set_service_jwt, set_user_jwt
    from src.agent.tools import search_dashboards

    set_user_jwt("test-jwt")
    set_service_jwt("svc-jwt")

    with patch("httpx.AsyncClient") as mock_client:
        mock_instance = AsyncMock()
        mock_client.return_value.__aenter__.return_value = mock_instance
        mock_instance.get.side_effect = Exception("Connection refused")

        # Should propagate the exception (caller handles error)
        with pytest.raises((Exception,)):
            await search_dashboards.ainvoke({"query": "test"})
# #endregion TestAgentChat.Tools.HttpFailure


# #region TestAgentChat.Tools.GetAll [C:2] [TYPE Function] [SEMANTICS test,tools,registry]
# @BRIEF get_all_tools returns the expected list of tool functions.

def test_get_all_tools_returns_expected_list():
    """get_all_tools() should return search_dashboards, get_health_summary, etc."""
    from src.agent.tools import get_all_tools

    tools = get_all_tools()
    assert len(tools) >= 4, f"Expected at least 4 tools, got {len(tools)}"

    tool_names = [t.name for t in tools]
    assert "search_dashboards" in tool_names
    assert "get_health_summary" in tool_names
    assert "list_environments" in tool_names
    assert "get_task_status" in tool_names


def test_get_all_tools_args_schema():
    """Tools with args_schema should have SearchDashboardsInput."""
    from src.agent.tools import get_all_tools

    tools = get_all_tools()
    search_tool = next(t for t in tools if t.name == "search_dashboards")
    assert search_tool.args_schema is not None, "search_dashboards should have args_schema"
    schema_fields = search_tool.args_schema.model_fields
    assert "query" in schema_fields, "search_dashboards should have 'query' field"
    assert schema_fields["query"].is_required(), "query should be required"
# #endregion TestAgentChat.Tools.GetAll


# #region TestAgentChat.Tools.ToolContracts [C:2] [TYPE Function] [SEMANTICS test,tools,contract]
# @BRIEF Tool contracts match @POST and @PRE declared in contracts/modules.md.

@pytest.mark.asyncio
async def test_search_dashboards_correct_url():
    """search_dashboards calls GET /api/dashboards with query params."""
    from src.agent.context import set_service_jwt, set_user_jwt
    from src.agent.tools import search_dashboards

    set_user_jwt("jwt")
    set_service_jwt("svc-jwt")

    with patch("httpx.AsyncClient") as mock_client:
        mock_instance = AsyncMock()
        mock_client.return_value.__aenter__.return_value = mock_instance
        mock_instance.get.return_value.text = '{"data": []}'

        await search_dashboards.ainvoke({"query": "dashboard-name", "env_id": "prod"})

        call_args = mock_instance.get.call_args
        assert call_args is not None
        args, kwargs = call_args
        url = args[0] if args else kwargs.get("url", "")
        assert "api/dashboards" in url
# #endregion TestAgentChat.Tools.ToolContracts


# #region TestAgentChat.Tools.HealthSummary [C:2] [TYPE Function] [SEMANTICS test,tools,health]
# @BRIEF get_health_summary calls the correct FastAPI endpoint.

@pytest.mark.asyncio
async def test_get_health_summary_calls_correct_url():
    """get_health_summary should call GET /api/dashboards/health."""
    from src.agent.context import set_service_jwt, set_user_jwt
    from src.agent.tools import get_health_summary

    set_user_jwt("jwt")
    set_service_jwt("svc-jwt")

    with patch("httpx.AsyncClient") as mock_client:
        mock_instance = AsyncMock()
        mock_client.return_value.__aenter__.return_value = mock_instance
        mock_instance.get.return_value.text = '{"status": "ok"}'

        await get_health_summary.ainvoke({})

        call_args = mock_instance.get.call_args
        assert call_args is not None
        args, kwargs = call_args
        url = args[0] if args else kwargs.get("url", "")
        assert "api/dashboards/health" in url
# #endregion TestAgentChat.Tools.HealthSummary


# #region TestAgentChat.Tools.ListEnvironments [C:2] [TYPE Function] [SEMANTICS test,tools,environments]
# @BRIEF list_environments calls the correct FastAPI endpoint.

@pytest.mark.asyncio
async def test_list_environments_calls_correct_url():
    """list_environments should call GET /api/settings/environments."""
    from src.agent.context import set_service_jwt, set_user_jwt
    from src.agent.tools import list_environments

    set_user_jwt("jwt")
    set_service_jwt("svc-jwt")

    with patch("httpx.AsyncClient") as mock_client:
        mock_instance = AsyncMock()
        mock_client.return_value.__aenter__.return_value = mock_instance
        mock_instance.get.return_value.text = '["prod", "dev"]'

        await list_environments.ainvoke({})

        call_args = mock_instance.get.call_args
        assert call_args is not None
        args, kwargs = call_args
        url = args[0] if args else kwargs.get("url", "")
        assert "api/settings/environments" in url
# #endregion TestAgentChat.Tools.ListEnvironments


# #region TestAgentChat.Tools.TaskStatus [C:2] [TYPE Function] [SEMANTICS test,tools,task]
# @BRIEF get_task_status calls the correct FastAPI endpoint with task_id.

@pytest.mark.asyncio
async def test_get_task_status_calls_correct_url():
    """get_task_status should call GET /api/tasks/{task_id}."""
    from src.agent.context import set_service_jwt, set_user_jwt
    from src.agent.tools import get_task_status

    set_user_jwt("jwt")
    set_service_jwt("svc-jwt")

    with patch("httpx.AsyncClient") as mock_client:
        mock_instance = AsyncMock()
        mock_client.return_value.__aenter__.return_value = mock_instance
        mock_instance.get.return_value.text = '{"status": "running"}'

        await get_task_status.ainvoke({"task_id": "task-123"})

        call_args = mock_instance.get.call_args
        assert call_args is not None
        args, kwargs = call_args
        url = args[0] if args else kwargs.get("url", "")
        assert "api/tasks/task-123" in url
# #endregion TestAgentChat.Tools.TaskStatus


# #region TestAgentChat.Tools.DualAuthHeaders [C:2] [TYPE Function] [SEMANTICS test,tools,auth,headers]
# @BRIEF _dual_auth_headers builds proper headers from ContextVars.

def test_dual_auth_headers_with_both_jwts():
    """_dual_auth_headers returns Authorization + X-User-JWT when both set."""
    from src.agent.context import set_service_jwt, set_user_jwt
    from src.agent.tools import _dual_auth_headers

    set_service_jwt("svc-token")
    set_user_jwt("user-token")

    headers = _dual_auth_headers()
    assert headers.get("Authorization") == "Bearer svc-token"
    assert headers.get("X-User-JWT") == "user-token"


def test_dual_auth_headers_no_user_jwt():
    """_dual_auth_headers returns only Authorization when no user JWT."""
    from src.agent.context import set_service_jwt, set_user_jwt
    from src.agent.tools import _dual_auth_headers

    set_service_jwt("svc-token")
    set_user_jwt("")

    headers = _dual_auth_headers()
    assert headers.get("Authorization") == "Bearer svc-token"
    assert "X-User-JWT" not in headers or headers.get("X-User-JWT") == ""


def test_dual_auth_headers_no_jwts(monkeypatch):
    """_dual_auth_headers returns empty dict when no JWTs."""
    monkeypatch.delenv("SERVICE_JWT", raising=False)
    from src.agent.context import set_service_jwt, set_user_jwt
    from src.agent.tools import _dual_auth_headers

    set_service_jwt("")
    set_user_jwt("")

    headers = _dual_auth_headers()
    assert headers == {}
# #endregion TestAgentChat.Tools.DualAuthHeaders
# #endregion TestAgentChat.Tools
