"""Tests for ATF introspection tools (atf_list_tests, atf_get_test, atf_list_suites, atf_get_results)."""

from urllib.parse import unquote

import httpx
import pytest
import respx
from toon_format import decode as toon_decode

from servicenow_mcp.auth import BasicAuthProvider
from servicenow_mcp.state import QueryTokenStore

BASE_URL = "https://test.service-now.com"


@pytest.fixture
def auth_provider(settings):
    """Create a BasicAuthProvider from test settings."""
    return BasicAuthProvider(settings)


def _register_and_get_tools(settings, auth_provider):
    """Helper: register testing tools on a fresh MCP server and return tool map + query store."""
    from mcp.server.fastmcp import FastMCP

    from servicenow_mcp.tools.testing import register_tools

    mcp = FastMCP("test")
    query_store = QueryTokenStore()
    mcp._sn_query_store = query_store  # type: ignore[attr-defined]
    register_tools(mcp, settings, auth_provider)
    return {t.name: t.fn for t in mcp._tool_manager._tools.values()}, query_store


class TestAtfListTests:
    """Tests for the atf_list_tests tool."""

    @pytest.mark.asyncio
    @respx.mock
    async def test_list_tests_success(self, settings, auth_provider):
        """Returns ATF tests with expected fields."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {
                            "sys_id": "test1",
                            "name": "Validate incident creation",
                            "description": "Test incident table automation",
                            "active": "true",
                            "sys_updated_on": "2026-02-20 10:00:00",
                            "test_origin": "manual",
                        },
                        {
                            "sys_id": "test2",
                            "name": "Check user permissions",
                            "description": "Verify ITIL role assignment",
                            "active": "true",
                            "sys_updated_on": "2026-02-21 14:30:00",
                            "test_origin": "automated",
                        },
                    ]
                },
                headers={"X-Total-Count": "2"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_list_tests"]()
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["record_count"] == 2
        assert result["data"]["records"][0]["sys_id"] == "test1"
        assert result["data"]["records"][1]["sys_id"] == "test2"

    @pytest.mark.asyncio
    @respx.mock
    async def test_list_tests_with_query(self, settings, auth_provider):
        """Verify custom query param is passed through to API call."""
        route = respx.get(f"{BASE_URL}/api/now/table/sys_atf_test").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {
                            "sys_id": "test1",
                            "name": "Active test only",
                            "description": "Test",
                            "active": "true",
                            "sys_updated_on": "2026-02-20 10:00:00",
                            "test_origin": "manual",
                        },
                    ]
                },
                headers={"X-Total-Count": "1"},
            )
        )

        tools, query_store = _register_and_get_tools(settings, auth_provider)
        token = query_store.create({"query": "active=true"})
        raw = await tools["atf_list_tests"](query_token=token)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert route.called
        request = route.calls.last.request
        assert "active=true" in unquote(str(request.url))

    @pytest.mark.asyncio
    @respx.mock
    async def test_list_tests_empty_results(self, settings, auth_provider):
        """Returns success with empty data when no tests found."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test").mock(
            return_value=httpx.Response(
                200,
                json={"result": []},
                headers={"X-Total-Count": "0"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_list_tests"]()
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["record_count"] == 0
        assert result["data"]["records"] == []


class TestAtfGetTest:
    """Tests for the atf_get_test tool."""

    @pytest.mark.asyncio
    @respx.mock
    async def test_get_test_success(self, settings, auth_provider):
        """Returns test record and steps in combined response."""
        # Mock test record
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test/test123").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": {
                        "sys_id": "test123",
                        "name": "Incident workflow test",
                        "description": "Validates incident state transitions",
                        "active": "true",
                    }
                },
            )
        )
        # Mock test steps
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_step").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {
                            "sys_id": "step1",
                            "display_name": "Open record",
                            "step_config": "Record Producer",
                            "order": "1",
                            "inputs": "{}",
                        },
                        {
                            "sys_id": "step2",
                            "display_name": "Validate state",
                            "step_config": "Field Values Validation",
                            "order": "2",
                            "inputs": '{"field": "state", "value": "1"}',
                        },
                    ]
                },
                headers={"X-Total-Count": "2"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_get_test"](test_id="test123")
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["test"]["sys_id"] == "test123"
        assert result["data"]["step_count"] == 2
        assert len(result["data"]["steps"]) == 2

    @pytest.mark.asyncio
    @respx.mock
    async def test_get_test_not_found(self, settings, auth_provider):
        """Returns error envelope when test not found."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test/missing").mock(
            return_value=httpx.Response(
                404,
                json={"error": {"message": "Record not found"}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_get_test"](test_id="missing")
        result = toon_decode(raw)

        assert result["status"] == "error"

    @pytest.mark.asyncio
    @respx.mock
    async def test_get_test_with_steps(self, settings, auth_provider):
        """Returns test with 3 steps ordered correctly."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test/test456").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": {
                        "sys_id": "test456",
                        "name": "Multi-step test",
                        "description": "Complex workflow",
                        "active": "true",
                    }
                },
            )
        )
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_step").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {
                            "sys_id": "s1",
                            "display_name": "Step 1",
                            "order": "1",
                            "step_config": "Config",
                            "inputs": "{}",
                        },
                        {
                            "sys_id": "s2",
                            "display_name": "Step 2",
                            "order": "2",
                            "step_config": "Config",
                            "inputs": "{}",
                        },
                        {
                            "sys_id": "s3",
                            "display_name": "Step 3",
                            "order": "3",
                            "step_config": "Config",
                            "inputs": "{}",
                        },
                    ]
                },
                headers={"X-Total-Count": "3"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_get_test"](test_id="test456")
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["step_count"] == 3
        assert result["data"]["steps"][0]["order"] == "1"
        assert result["data"]["steps"][1]["order"] == "2"
        assert result["data"]["steps"][2]["order"] == "3"


class TestAtfListSuites:
    """Tests for the atf_list_suites tool."""

    @pytest.mark.asyncio
    @respx.mock
    async def test_list_suites_success(self, settings, auth_provider):
        """Returns suites with member counts."""
        # Mock suite query
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_suite").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {
                            "sys_id": "suite1",
                            "name": "Regression Suite",
                            "description": "Full regression tests",
                            "active": "true",
                            "sys_updated_on": "2026-02-20 09:00:00",
                        },
                    ]
                },
                headers={"X-Total-Count": "1"},
            )
        )
        # Mock member count aggregation
        respx.get(f"{BASE_URL}/api/now/stats/sys_atf_test_suite_test").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"stats": {"count": "5"}}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_list_suites"]()
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["record_count"] == 1
        assert result["data"]["suites"][0]["sys_id"] == "suite1"
        assert result["data"]["suites"][0]["member_count"] == "5"

    @pytest.mark.asyncio
    @respx.mock
    async def test_list_suites_empty(self, settings, auth_provider):
        """Returns success with empty data when no suites found."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_suite").mock(
            return_value=httpx.Response(
                200,
                json={"result": []},
                headers={"X-Total-Count": "0"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_list_suites"]()
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["record_count"] == 0
        assert result["data"]["suites"] == []


class TestAtfGetResults:
    """Tests for the atf_get_results tool."""

    @pytest.mark.asyncio
    @respx.mock
    async def test_get_results_for_test(self, settings, auth_provider):
        """Returns test results from sys_atf_test_result table."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_result").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {
                            "sys_id": "result1",
                            "status": "success",
                            "start_time": "2026-02-20 10:00:00",
                            "end_time": "2026-02-20 10:00:15",
                            "run_time": "15",
                            "output": "All steps passed",
                            "first_failing_step": "",
                        },
                    ]
                },
                headers={"X-Total-Count": "1"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_get_results"](test_id="test123")
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["result_type"] == "test_results"
        assert result["data"]["result_count"] == 1
        assert result["data"]["results"][0]["status"] == "success"

    @pytest.mark.asyncio
    @respx.mock
    async def test_get_results_for_suite(self, settings, auth_provider):
        """Returns suite results from sys_atf_test_suite_result table."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_suite_result").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {
                            "sys_id": "suite_result1",
                            "status": "failure",
                            "start_time": "2026-02-20 11:00:00",
                            "end_time": "2026-02-20 11:05:00",
                            "success_count": "3",
                            "failure_count": "1",
                            "error_count": "0",
                            "skipped_count": "0",
                        },
                    ]
                },
                headers={"X-Total-Count": "1"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_get_results"](suite_id="suite456")
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["result_type"] == "suite_results"
        assert result["data"]["result_count"] == 1
        assert result["data"]["results"][0]["status"] == "failure"

    @pytest.mark.asyncio
    async def test_get_results_both_ids_provided(self, settings, auth_provider):
        """Returns error when both test_id and suite_id are provided."""
        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_get_results"](test_id="test123", suite_id="suite456")
        result = toon_decode(raw)

        assert result["status"] == "error"
        assert "not both" in result["error"].lower()

    @pytest.mark.asyncio
    async def test_get_results_missing_both_ids(self, settings, auth_provider):
        """Returns error when neither test_id nor suite_id provided."""
        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_get_results"]()
        result = toon_decode(raw)

        assert result["status"] == "error"
        assert "exactly one" in result["error"].lower()

    @pytest.mark.asyncio
    @respx.mock
    async def test_get_results_empty(self, settings, auth_provider):
        """Returns success with empty results when no results found."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_result").mock(
            return_value=httpx.Response(
                200,
                json={"result": []},
                headers={"X-Total-Count": "0"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_get_results"](test_id="test999")
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["result_count"] == 0
        assert result["data"]["results"] == []


class TestAtfRunTest:
    """Tests for the atf_run_test tool."""

    @pytest.mark.asyncio
    @respx.mock
    async def test_run_test_success_with_poll(self, settings, auth_provider):
        """Mock run + progress endpoints. Returns completed status with execution ID."""
        # Mock ATF run endpoint
        respx.post(f"{BASE_URL}/api/now/sn_atf_tg/test_runner").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"snboqId": "exec123"}},
            )
        )
        # Mock progress endpoint - completed immediately
        respx.get(f"{BASE_URL}/api/now/sn_atf_tg/test_runner_progress").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"state": "Completed", "progress": 100}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_run_test"](test_id="test456", poll=True, poll_interval=2, max_poll_duration=10)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["execution_id"] == "exec123"
        assert result["data"]["status"] == "completed"
        assert result["data"]["progress"] == 100
        assert result["data"]["test_id"] == "test456"

    @pytest.mark.asyncio
    @respx.mock
    async def test_run_test_no_poll(self, settings, auth_provider):
        """Call with poll=False. Returns immediately with execution ID."""
        respx.post(f"{BASE_URL}/api/now/sn_atf_tg/test_runner").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"snboqId": "exec789"}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_run_test"](test_id="test999", poll=False)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["execution_id"] == "exec789"
        assert result["data"]["status"] == "started"
        assert result["data"]["polling"] is False
        assert result["data"]["test_id"] == "test999"

    @pytest.mark.asyncio
    async def test_run_test_write_gate_blocks(self, prod_settings):
        """Write gate blocks execution in production environment."""
        from mcp.server.fastmcp import FastMCP

        from servicenow_mcp.tools.testing import register_tools

        prod_auth = BasicAuthProvider(prod_settings)
        mcp = FastMCP("test")
        mcp._sn_query_store = QueryTokenStore()  # type: ignore[attr-defined]
        register_tools(mcp, prod_settings, prod_auth)
        tools = {t.name: t.fn for t in mcp._tool_manager._tools.values()}

        raw = await tools["atf_run_test"](test_id="test123")
        result = toon_decode(raw)

        assert result["status"] == "error"
        assert "production" in result["error"].lower() or "blocked" in result["error"].lower()

    @pytest.mark.asyncio
    @respx.mock
    async def test_run_test_no_execution_id(self, settings, auth_provider):
        """Returns error when ATF run does not return an execution ID."""
        respx.post(f"{BASE_URL}/api/now/sn_atf_tg/test_runner").mock(
            return_value=httpx.Response(
                200,
                json={"result": {}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_run_test"](test_id="test_no_id", poll=True)
        result = toon_decode(raw)

        assert result["status"] == "error"
        assert "no execution id" in result["error"].lower()

    @pytest.mark.asyncio
    @respx.mock
    async def test_run_test_timeout(self, settings, auth_provider):
        """Mock progress endpoint always returning In Progress. Assert timeout."""
        respx.post(f"{BASE_URL}/api/now/sn_atf_tg/test_runner").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"snboqId": "exec_timeout"}},
            )
        )
        # Progress always in progress
        respx.get(f"{BASE_URL}/api/now/sn_atf_tg/test_runner_progress").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"state": "In Progress", "progress": 50}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_run_test"](test_id="test_long", poll=True, poll_interval=2, max_poll_duration=10)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["status"] == "polling_timeout"
        assert result["data"]["execution_id"] == "exec_timeout"
        assert result["data"]["last_known_state"] == "in progress"
        assert "warnings" in result

    @pytest.mark.asyncio
    @respx.mock
    async def test_run_test_failed_execution(self, settings, auth_provider):
        """Mock progress returning Failure status. Assert failure captured."""
        respx.post(f"{BASE_URL}/api/now/sn_atf_tg/test_runner").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"snboqId": "exec_fail"}},
            )
        )
        respx.get(f"{BASE_URL}/api/now/sn_atf_tg/test_runner_progress").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"state": "Failure", "progress": 100}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_run_test"](test_id="test_fail", poll=True, poll_interval=2, max_poll_duration=10)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["status"] == "failure"
        assert result["data"]["execution_id"] == "exec_fail"


class TestAtfRunSuite:
    """Tests for the atf_run_suite tool."""

    @pytest.mark.asyncio
    @respx.mock
    async def test_run_suite_success_with_poll(self, settings, auth_provider):
        """Mock run + progress. Returns completed status."""
        respx.post(f"{BASE_URL}/api/now/sn_atf_tg/test_runner").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"snboqId": "suite_exec123"}},
            )
        )
        respx.get(f"{BASE_URL}/api/now/sn_atf_tg/test_runner_progress").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"state": "Completed", "progress": 100}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_run_suite"](suite_id="suite789", poll=True, poll_interval=2, max_poll_duration=10)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["execution_id"] == "suite_exec123"
        assert result["data"]["status"] == "completed"
        assert result["data"]["suite_id"] == "suite789"

    @pytest.mark.asyncio
    @respx.mock
    async def test_run_suite_no_poll(self, settings, auth_provider):
        """Call with poll=False. Returns immediately."""
        respx.post(f"{BASE_URL}/api/now/sn_atf_tg/test_runner").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"snboqId": "suite_no_poll"}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_run_suite"](suite_id="suite_fast", poll=False)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["execution_id"] == "suite_no_poll"
        assert result["data"]["status"] == "started"
        assert result["data"]["polling"] is False

    @pytest.mark.asyncio
    async def test_run_suite_write_gate_blocks(self, prod_settings):
        """Write gate blocks suite execution in production."""
        from mcp.server.fastmcp import FastMCP

        from servicenow_mcp.tools.testing import register_tools

        prod_auth = BasicAuthProvider(prod_settings)
        mcp = FastMCP("test")
        mcp._sn_query_store = QueryTokenStore()  # type: ignore[attr-defined]
        register_tools(mcp, prod_settings, prod_auth)
        tools = {t.name: t.fn for t in mcp._tool_manager._tools.values()}

        raw = await tools["atf_run_suite"](suite_id="suite999")
        result = toon_decode(raw)

        assert result["status"] == "error"
        assert "production" in result["error"].lower() or "blocked" in result["error"].lower()

    @pytest.mark.asyncio
    @respx.mock
    async def test_run_suite_no_execution_id(self, settings, auth_provider):
        """Returns error when ATF suite run does not return an execution ID."""
        respx.post(f"{BASE_URL}/api/now/sn_atf_tg/test_runner").mock(
            return_value=httpx.Response(
                200,
                json={"result": {}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_run_suite"](suite_id="suite_no_id", poll=True)
        result = toon_decode(raw)

        assert result["status"] == "error"
        assert "no execution id" in result["error"].lower()

    @pytest.mark.asyncio
    @respx.mock
    async def test_run_suite_timeout(self, settings, auth_provider):
        """Mock progress always returning In Progress. Assert timeout with warnings."""
        respx.post(f"{BASE_URL}/api/now/sn_atf_tg/test_runner").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"snboqId": "suite_exec_timeout"}},
            )
        )
        respx.get(f"{BASE_URL}/api/now/sn_atf_tg/test_runner_progress").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"state": "In Progress", "progress": 50}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_run_suite"](suite_id="suite_long", poll=True, poll_interval=2, max_poll_duration=10)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["status"] == "polling_timeout"
        assert result["data"]["execution_id"] == "suite_exec_timeout"
        assert result["data"]["suite_id"] == "suite_long"
        assert result["data"]["last_known_state"] == "in progress"
        assert "warnings" in result

    @pytest.mark.asyncio
    @respx.mock
    async def test_run_suite_cancelled(self, settings, auth_provider):
        """Mock progress returning Cancelled. Assert captured in response."""
        respx.post(f"{BASE_URL}/api/now/sn_atf_tg/test_runner").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"snboqId": "suite_cancel"}},
            )
        )
        respx.get(f"{BASE_URL}/api/now/sn_atf_tg/test_runner_progress").mock(
            return_value=httpx.Response(
                200,
                json={"result": {"state": "Cancelled", "progress": 0}},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_run_suite"](suite_id="suite_cancel", poll=True, poll_interval=2, max_poll_duration=10)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["status"] == "cancelled"
        assert result["data"]["execution_id"] == "suite_cancel"


class TestAtfTestHealth:
    """Tests for the atf_test_health tool."""

    @pytest.mark.asyncio
    @respx.mock
    async def test_health_all_passing(self, settings, auth_provider):
        """Mock 10 results all passed. Assert 100% pass rate, not flaky."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_result").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {"sys_id": f"res{i}", "status": "success", "sys_created_on": f"2026-02-{i + 1:02d} 10:00:00"}
                        for i in range(10)
                    ]
                },
                headers={"X-Total-Count": "10"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_test_health"](test_id="test_stable", days=30, limit=50)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["total_runs"] == 10
        assert result["data"]["pass_count"] == 10
        assert result["data"]["fail_count"] == 0
        assert result["data"]["pass_rate"] == 1.0
        assert result["data"]["flaky"] is False

    @pytest.mark.asyncio
    @respx.mock
    async def test_health_flaky_detection(self, settings, auth_provider):
        """Mock alternating pass/fail pattern. Assert flaky=true."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_result").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {
                            "sys_id": f"res{i}",
                            "status": "success" if i % 2 == 0 else "failure",
                            "sys_created_on": f"2026-02-{i + 1:02d} 10:00:00",
                        }
                        for i in range(10)
                    ]
                },
                headers={"X-Total-Count": "10"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_test_health"](test_id="test_flaky", days=30, limit=50)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["flaky"] is True
        assert result["data"]["transition_count"] >= 3

    @pytest.mark.asyncio
    @respx.mock
    async def test_health_trending_downward(self, settings, auth_provider):
        """Mock recent failures after earlier passes. Assert trend degrading."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_result").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {"sys_id": "r1", "status": "success", "sys_created_on": "2026-02-01 10:00:00"},
                        {"sys_id": "r2", "status": "success", "sys_created_on": "2026-02-02 10:00:00"},
                        {"sys_id": "r3", "status": "success", "sys_created_on": "2026-02-03 10:00:00"},
                        {"sys_id": "r4", "status": "success", "sys_created_on": "2026-02-04 10:00:00"},
                        {"sys_id": "r5", "status": "failure", "sys_created_on": "2026-02-05 10:00:00"},
                        {"sys_id": "r6", "status": "failure", "sys_created_on": "2026-02-06 10:00:00"},
                        {"sys_id": "r7", "status": "failure", "sys_created_on": "2026-02-07 10:00:00"},
                        {"sys_id": "r8", "status": "failure", "sys_created_on": "2026-02-08 10:00:00"},
                    ]
                },
                headers={"X-Total-Count": "8"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_test_health"](test_id="test_degrading", days=30, limit=50)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["recent_trend"] == "degrading"
        assert result["data"]["pass_rate"] == 0.5

    @pytest.mark.asyncio
    @respx.mock
    async def test_health_no_results(self, settings, auth_provider):
        """Mock empty results. Assert appropriate zero-state response."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_result").mock(
            return_value=httpx.Response(
                200,
                json={"result": []},
                headers={"X-Total-Count": "0"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_test_health"](test_id="test_no_data", days=30, limit=50)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["total_runs"] == 0
        assert result["data"]["pass_rate"] == 0.0
        assert result["data"]["recent_trend"] == "no_data"
        assert "warnings" in result

    @pytest.mark.asyncio
    async def test_health_missing_both_ids(self, settings, auth_provider):
        """Call with neither test_id nor suite_id. Assert error."""
        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_test_health"]()
        result = toon_decode(raw)

        assert result["status"] == "error"
        assert "exactly one" in result["error"].lower()

    @pytest.mark.asyncio
    async def test_health_both_ids_provided(self, settings, auth_provider):
        """Call with both test_id and suite_id. Assert error."""
        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_test_health"](test_id="test123", suite_id="suite456")
        result = toon_decode(raw)

        assert result["status"] == "error"
        assert "exactly one" in result["error"].lower() and "not both" in result["error"].lower()

    @pytest.mark.asyncio
    @respx.mock
    async def test_health_trending_upward(self, settings, auth_provider):
        """Mock early failures then later passes. Assert trend improving."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_result").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {"sys_id": "r1", "status": "failure", "sys_created_on": "2026-02-01 10:00:00"},
                        {"sys_id": "r2", "status": "failure", "sys_created_on": "2026-02-02 10:00:00"},
                        {"sys_id": "r3", "status": "failure", "sys_created_on": "2026-02-03 10:00:00"},
                        {"sys_id": "r4", "status": "failure", "sys_created_on": "2026-02-04 10:00:00"},
                        {"sys_id": "r5", "status": "success", "sys_created_on": "2026-02-05 10:00:00"},
                        {"sys_id": "r6", "status": "success", "sys_created_on": "2026-02-06 10:00:00"},
                        {"sys_id": "r7", "status": "success", "sys_created_on": "2026-02-07 10:00:00"},
                        {"sys_id": "r8", "status": "success", "sys_created_on": "2026-02-08 10:00:00"},
                    ]
                },
                headers={"X-Total-Count": "8"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_test_health"](test_id="test_improving", days=30, limit=50)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["recent_trend"] == "improving"
        assert result["data"]["pass_rate"] == 0.5

    @pytest.mark.asyncio
    @respx.mock
    async def test_health_insufficient_data_trend(self, settings, auth_provider):
        """Mock fewer than 4 runs. Assert trend is insufficient_data."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_result").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {"sys_id": "r1", "status": "success", "sys_created_on": "2026-02-01 10:00:00"},
                        {"sys_id": "r2", "status": "failure", "sys_created_on": "2026-02-02 10:00:00"},
                    ]
                },
                headers={"X-Total-Count": "2"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_test_health"](test_id="test_few_runs", days=30, limit=50)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["total_runs"] == 2
        assert result["data"]["recent_trend"] == "insufficient_data"

    @pytest.mark.asyncio
    @respx.mock
    async def test_health_for_suite(self, settings, auth_provider):
        """Mock suite results. Assert rolled-up metrics computed."""
        respx.get(f"{BASE_URL}/api/now/table/sys_atf_test_suite_result").mock(
            return_value=httpx.Response(
                200,
                json={
                    "result": [
                        {
                            "sys_id": f"suite_res{i}",
                            "status": "passed",
                            "sys_created_on": f"2026-02-{i + 1:02d} 10:00:00",
                        }
                        for i in range(5)
                    ]
                },
                headers={"X-Total-Count": "5"},
            )
        )

        tools, _query_store = _register_and_get_tools(settings, auth_provider)
        raw = await tools["atf_test_health"](suite_id="suite_healthy", days=30, limit=50)
        result = toon_decode(raw)

        assert result["status"] == "success"
        assert result["data"]["total_runs"] == 5
        assert result["data"]["pass_count"] == 5
        assert result["data"]["pass_rate"] == 1.0
