# #region Test.Assistant.Tools [C:2] [TYPE Module] [SEMANTICS test,assistant,tools,handlers]
# @BRIEF Tests for all assistant tool handlers, registry, resolvers, dispatch, and routes.
# @RELATION BINDS_TO -> [AssistantToolRegistry]
# @RELATION BINDS_TO -> [AssistantResolvers]
# @RELATION BINDS_TO -> [AssistantDispatch]
# @RELATION BINDS_TO -> [AssistantRoutes]

from __future__ import annotations

from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from fastapi import HTTPException

from src.schemas.auth import User


# ── Shared fixtures ──────────────────────────────────────────────────────────


def _make_env(id_: str, name_: str, stage="DEV", is_production=False, is_default=False):
    """Create a mock environment object with proper attribute access."""
    env = MagicMock()
    env.id = id_
    env.name = name_
    env.stage = stage
    env.is_production = is_production
    env.is_default = is_default
    return env


@pytest.fixture
def current_user():
    return User(
        id="user-1",
        username="testuser",
        email="test@example.com",
        is_active=True,
        auth_source="internal",
        created_at=datetime.now(UTC),
        last_login=datetime.now(UTC),
        roles=[],
    )


@pytest.fixture
def task_manager():
    tm = MagicMock()
    tm.create_task = AsyncMock(return_value=MagicMock(id="task-42"))
    tm.get_task = MagicMock(return_value=None)
    tm.get_tasks = MagicMock(return_value=[])
    return tm


@pytest.fixture
def config_manager():
    cm = MagicMock()
    cm.get_environments = MagicMock(return_value=[])
    cm.get_config = MagicMock()
    return cm


@pytest.fixture
def db():
    return MagicMock()


@pytest.fixture
def intent():
    return {"entities": {}}


# ── _tool_capabilities.py ────────────────────────────────────────────────────


class TestToolCapabilities:
    """handle_show_capabilities — list available commands."""

    # #region test_show_capabilities_with_tools [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_capabilities.get_catalog")
    async def test_show_capabilities_with_tools(
        self, mock_get_catalog, intent, current_user, task_manager, config_manager, db
    ):
        from src.api.routes.assistant._tool_capabilities import handle_show_capabilities

        mock_get_catalog.return_value = [
            {"operation": "create_branch"},
            {"operation": "commit_changes"},
            {"operation": "deploy_dashboard"},
            {"operation": "execute_migration"},
            {"operation": "run_backup"},
            {"operation": "run_llm_validation"},
            {"operation": "run_llm_documentation"},
            {"operation": "get_task_status"},
            {"operation": "get_health_summary"},
        ]
        text, task_id, actions = await handle_show_capabilities(
            intent, current_user, task_manager, config_manager, db
        )
        assert "Вот что я могу сделать для вас" in text
        assert task_id is None
        assert actions == []
    # #endregion test_show_capabilities_with_tools

    # #region test_show_capabilities_empty [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_capabilities.get_catalog")
    async def test_show_capabilities_empty(
        self, mock_get_catalog, intent, current_user, task_manager, config_manager, db
    ):
        from src.api.routes.assistant._tool_capabilities import handle_show_capabilities

        mock_get_catalog.return_value = [{"operation": "unknown_op"}]
        text, task_id, actions = await handle_show_capabilities(
            intent, current_user, task_manager, config_manager, db
        )
        assert "нет доступных" in text.lower()
        assert task_id is None
    # #endregion test_show_capabilities_empty


# ── _tool_commit.py ──────────────────────────────────────────────────────────


class TestToolCommit:
    """handle_commit_changes — commit dashboard changes."""

    # #region test_commit_changes_success [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_commit._get_git_service")
    @patch("src.api.routes.assistant._tool_commit._check_any_permission")
    @patch("src.api.routes.assistant._tool_commit._resolve_dashboard_id_entity")
    async def test_commit_changes_success(
        self, mock_resolve, mock_check, mock_git_service,
        current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_commit import handle_commit_changes

        mock_resolve.return_value = 42
        mock_git = MagicMock()
        mock_git_service.return_value = mock_git

        intent_data = {"entities": {"dashboard_id": "42", "message": "my commit message"}}
        text, task_id, actions = await handle_commit_changes(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "Коммит выполнен" in text
        mock_git.commit_changes.assert_called_with(42, "my commit message", None)
    # #endregion test_commit_changes_success

    # #region test_commit_changes_missing_dashboard [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_commit._check_any_permission")
    @patch("src.api.routes.assistant._tool_commit._resolve_dashboard_id_entity")
    async def test_commit_changes_missing_dashboard(
        self, mock_resolve, mock_check,
        current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_commit import handle_commit_changes

        mock_resolve.return_value = None

        with pytest.raises(HTTPException) as exc:
            await handle_commit_changes(
                {"entities": {}}, current_user, task_manager, config_manager, db
            )
        assert exc.value.status_code == 422
    # #endregion test_commit_changes_missing_dashboard


# ── _tool_create_branch.py ────────────────────────────────────────────────────


class TestToolCreateBranch:
    """handle_create_branch — create git branch."""

    # #region test_create_branch_success [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_create_branch._get_git_service")
    @patch("src.api.routes.assistant._tool_create_branch._check_any_permission")
    @patch("src.api.routes.assistant._tool_create_branch._resolve_dashboard_id_entity")
    async def test_create_branch_success(
        self, mock_resolve, mock_check, mock_git_service,
        current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_create_branch import handle_create_branch

        mock_resolve.return_value = 42
        mock_git = MagicMock()
        mock_git_service.return_value = mock_git

        intent_data = {"entities": {"dashboard_id": "42", "branch_name": "feature/test"}}
        text, task_id, actions = await handle_create_branch(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "Ветка" in text
        assert "feature/test" in text
        assert "42" in text
        mock_git.create_branch.assert_called_with(42, "feature/test", "main")
    # #endregion test_create_branch_success

    # #region test_create_branch_missing_params [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_create_branch._check_any_permission", return_value=True)
    @patch("src.api.routes.assistant._tool_create_branch._resolve_dashboard_id_entity")
    async def test_create_branch_missing_params(
        self, mock_resolve, mock_perm,
        current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_create_branch import handle_create_branch

        mock_resolve.return_value = None

        with pytest.raises(HTTPException) as exc:
            await handle_create_branch(
                {"entities": {}}, current_user, task_manager, config_manager, db
            )
        assert exc.value.status_code == 422
    # #endregion test_create_branch_missing_params


# ── _tool_deploy.py ───────────────────────────────────────────────────────────


class TestToolDeploy:
    """handle_deploy_dashboard — deploy dashboard to environment."""

    # #region test_deploy_dashboard_success [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_deploy._check_any_permission")
    @patch("src.api.routes.assistant._tool_deploy._resolve_dashboard_id_entity")
    @patch("src.api.routes.assistant._tool_deploy._resolve_env_id")
    async def test_deploy_dashboard_success(
        self, mock_env, mock_resolve, mock_check,
        task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_deploy import handle_deploy_dashboard

        mock_env.return_value = "env-prod"
        mock_resolve.return_value = 42

        intent_data = {"entities": {"environment": "prod", "dashboard_id": "42"}}
        text, task_id, actions = await handle_deploy_dashboard(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "Деплой запущен" in text
        assert task_id == "task-42"
        assert len(actions) == 2
        task_manager.create_task.assert_awaited_once()
    # #endregion test_deploy_dashboard_success

    # #region test_deploy_dashboard_missing_params [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_deploy._check_any_permission", return_value=True)
    @patch("src.api.routes.assistant._tool_deploy._resolve_env_id")
    @patch("src.api.routes.assistant._tool_deploy._resolve_dashboard_id_entity")
    async def test_deploy_dashboard_missing_params(
        self, mock_resolve_dash, mock_resolve_env, mock_perm,
        current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_deploy import handle_deploy_dashboard

        mock_resolve_dash.return_value = None

        with pytest.raises(HTTPException) as exc:
            await handle_deploy_dashboard(
                {"entities": {}}, current_user, task_manager, config_manager, db
            )
        assert exc.value.status_code == 422
    # #endregion test_deploy_dashboard_missing_params


# ── _tool_llm_documentation.py ────────────────────────────────────────────────


class TestToolLlmDocumentation:
    """handle_run_llm_documentation — generate LLM documentation."""

    # #region test_llm_documentation_success [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_llm_documentation._check_any_permission")
    @patch("src.api.routes.assistant._tool_llm_documentation._resolve_env_id")
    @patch("src.api.routes.assistant._tool_llm_documentation._resolve_provider_id")
    async def test_llm_documentation_success(
        self, mock_provider, mock_env, mock_check,
        task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_llm_documentation import handle_run_llm_documentation

        mock_env.return_value = "env-dev"
        mock_provider.return_value = "provider-1"

        intent_data = {"entities": {"dataset_id": "ds-1", "environment": "dev", "provider": "openai"}}
        text, task_id, actions = await handle_run_llm_documentation(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "Генерация документации запущена" in text
        assert task_id == "task-42"
        assert len(actions) == 2
    # #endregion test_llm_documentation_success

    # #region test_llm_documentation_missing_params [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_llm_documentation._check_any_permission", return_value=True)
    async def test_llm_documentation_missing_params(
        self, mock_perm,
        current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_llm_documentation import handle_run_llm_documentation

        with pytest.raises(HTTPException) as exc:
            await handle_run_llm_documentation(
                {"entities": {}}, current_user, task_manager, config_manager, db
            )
        assert exc.value.status_code == 400
    # #endregion test_llm_documentation_missing_params


# ── _tool_list_environments.py ────────────────────────────────────────────────


class TestToolListEnvironments:
    """handle_list_environments — list configured environments."""

    # #region test_list_environments_with_envs [C:2] [TYPE Function]
    async def test_list_environments_with_envs(
        self, intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_list_environments import handle_list_environments

        env1 = MagicMock(id="dev", name="Development", stage="DEV", is_production=False, is_default=True)
        env2 = MagicMock(id="prod", name="Production", stage="PROD", is_production=True, is_default=False)
        config_manager.get_environments.return_value = [env1, env2]

        text, task_id, actions = await handle_list_environments(
            intent, current_user, task_manager, config_manager, db
        )
        assert "Доступные окружения" in text
        assert "dev" in text
        assert "prod" in text
        assert task_id is None
        assert len(actions) == 1
    # #endregion test_list_environments_with_envs

    # #region test_list_environments_empty [C:2] [TYPE Function]
    async def test_list_environments_empty(
        self, intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_list_environments import handle_list_environments

        config_manager.get_environments.return_value = []

        text, task_id, actions = await handle_list_environments(
            intent, current_user, task_manager, config_manager, db
        )
        assert "Нет доступных окружений" in text
        assert task_id is None
    # #endregion test_list_environments_empty


# ── _tool_health_summary.py ───────────────────────────────────────────────────


class TestToolHealthSummary:
    """handle_get_health_summary — dashboard health summary."""

    # #region test_health_summary_success [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_health_summary.HealthService")
    @patch("src.api.routes.assistant._tool_health_summary._resolve_env_id")
    @patch("src.api.routes.assistant._tool_health_summary._get_environment_name_by_id")
    async def test_health_summary_success(
        self, mock_env_name, mock_env, mock_health,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_health_summary import handle_get_health_summary

        mock_env.return_value = "env-dev"
        mock_env_name.return_value = "Development"

        health_svc = MagicMock()
        mock_health.return_value = health_svc
        summary = MagicMock(
            pass_count=5, warn_count=2, fail_count=0, unknown_count=1, items=[]
        )
        health_svc.get_health_summary = AsyncMock(return_value=summary)

        text, task_id, actions = await handle_get_health_summary(
            intent, current_user, task_manager, config_manager, db
        )
        assert "Сводка здоровья" in text
        assert "5" in text
        assert task_id is None
        assert len(actions) == 1
    # #endregion test_health_summary_success

    # #region test_health_summary_with_failures [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_health_summary.HealthService")
    @patch("src.api.routes.assistant._tool_health_summary._resolve_env_id")
    @patch("src.api.routes.assistant._tool_health_summary._get_environment_name_by_id")
    async def test_health_summary_with_failures(
        self, mock_env_name, mock_env, mock_health,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_health_summary import handle_get_health_summary

        mock_env.return_value = None
        mock_env_name.return_value = "всех окружений"

        health_svc = MagicMock()
        mock_health.return_value = health_svc
        item1 = MagicMock(status="FAIL", dashboard_id="dash-1", environment_id="env-dev", summary="Broken", task_id="task-1")
        summary = MagicMock(
            pass_count=3, warn_count=1, fail_count=2, unknown_count=0,
            items=[item1],
        )
        health_svc.get_health_summary = AsyncMock(return_value=summary)

        text, task_id, actions = await handle_get_health_summary(
            intent, current_user, task_manager, config_manager, db
        )
        assert "Обнаружены ошибки" in text
        assert "dash-1" in text
        assert len(actions) >= 2
    # #endregion test_health_summary_with_failures


# ── _tool_backup.py ───────────────────────────────────────────────────────────


class TestToolBackup:
    """handle_run_backup — run backup."""

    # #region test_run_backup_env_only [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_backup._check_any_permission")
    @patch("src.api.routes.assistant._tool_backup._resolve_env_id")
    async def test_run_backup_env_only(
        self, mock_env, mock_check,
        task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_backup import handle_run_backup

        mock_env.return_value = "env-dev"

        intent_data = {"entities": {"environment": "dev"}}
        text, task_id, actions = await handle_run_backup(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "Бэкап запущен" in text
        assert task_id == "task-42"
        task_manager.create_task.assert_awaited_once_with(
            plugin_id="superset-backup",
            params={"environment_id": "env-dev"},
            user_id=current_user.id,
        )
    # #endregion test_run_backup_env_only

    # #region test_run_backup_with_dashboard [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_backup._check_any_permission")
    @patch("src.api.routes.assistant._tool_backup._resolve_dashboard_id_entity")
    @patch("src.api.routes.assistant._tool_backup._resolve_env_id")
    @patch("src.api.routes.assistant._tool_backup._get_environment_name_by_id")
    async def test_run_backup_with_dashboard(
        self, mock_env_name, mock_env, mock_resolve, mock_check,
        task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_backup import handle_run_backup

        mock_env.return_value = "env-dev"
        mock_env_name.return_value = "Development"
        mock_resolve.return_value = 42

        intent_data = {"entities": {"environment": "dev", "dashboard_id": "42"}}
        text, task_id, actions = await handle_run_backup(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "Бэкап запущен" in text
        assert task_id == "task-42"
        assert len(actions) == 4
    # #endregion test_run_backup_with_dashboard

    # #region test_run_backup_missing_env [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_backup._check_any_permission", return_value=True)
    @patch("src.api.routes.assistant._tool_backup._resolve_env_id", return_value=None)
    async def test_run_backup_missing_env(
        self, mock_resolve_env, mock_perm,
        task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_backup import handle_run_backup

        with pytest.raises(HTTPException) as exc:
            await handle_run_backup(
                {"entities": {}}, current_user, task_manager, config_manager, db
            )
        assert exc.value.status_code == 400
    # #endregion test_run_backup_missing_env


# ── _tool_migration.py ─────────────────────────────────────────────────────────


class TestToolMigration:
    """handle_execute_migration — execute migration."""

    # #region test_execute_migration_missing_envs [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_migration._check_any_permission", return_value=True)
    async def test_execute_migration_missing_envs(
        self, mock_perm,
        task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_migration import handle_execute_migration

        with pytest.raises(HTTPException) as exc:
            await handle_execute_migration(
                {"entities": {}}, current_user, task_manager, config_manager, db
            )
        assert exc.value.status_code == 422
    # #endregion test_execute_migration_missing_envs

    # #region test_execute_migration_success [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_migration._check_any_permission", return_value=True)
    @patch("src.api.routes.assistant._tool_migration._resolve_env_id", return_value="env-1")
    @patch("src.api.routes.assistant._tool_migration._resolve_dashboard_id_entity", return_value=42)
    async def test_execute_migration_success(
        self, mock_dash, mock_env, mock_perm,
        task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_migration import handle_execute_migration

        task_manager.create_task.return_value = MagicMock(id="task-99")

        text, task_id, actions = await handle_execute_migration(
            {"entities": {"environment": "prod", "dashboard_id": "42"}},
            current_user, task_manager, config_manager, db,
        )
        assert "Миграция запущена" in text
        assert task_id == "task-99"
    # #endregion test_execute_migration_success


# ── _tool_task_status.py ──────────────────────────────────────────────────────


class TestToolTaskStatus:
    """handle_get_task_status — get task status."""

    # #region test_get_task_status_by_id [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_task_status._check_any_permission")
    @patch("src.api.routes.assistant._tool_task_status._build_task_observability_summary")
    @patch("src.api.routes.assistant._tool_task_status._extract_result_deep_links")
    async def test_get_task_status_by_id(
        self, mock_links, mock_summary, mock_check,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_task_status import handle_get_task_status

        mock_summary.return_value = "Detailed summary"
        mock_links.return_value = []

        task = MagicMock(id="task-42", status="RUNNING", user_id=current_user.id)
        task_manager.get_task.return_value = task

        intent_data = {"entities": {"task_id": "task-42"}}
        text, task_id, actions = await handle_get_task_status(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "task-42" in text
        assert "RUNNING" in text
        assert task_id == "task-42"
    # #endregion test_get_task_status_by_id

    # #region test_get_task_status_latest [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_task_status._check_any_permission")
    @patch("src.api.routes.assistant._tool_task_status._build_task_observability_summary")
    @patch("src.api.routes.assistant._tool_task_status._extract_result_deep_links")
    async def test_get_task_status_latest(
        self, mock_links, mock_summary, mock_check,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_task_status import handle_get_task_status

        mock_summary.return_value = ""
        mock_links.return_value = []

        task = MagicMock(id="task-1", status="SUCCESS", user_id=current_user.id)
        task_manager.get_tasks.return_value = [task]

        text, task_id, actions = await handle_get_task_status(
            intent, current_user, task_manager, config_manager, db
        )
        assert "Последняя задача" in text
        assert task_id == "task-1"
    # #endregion test_get_task_status_latest

    # #region test_get_task_status_no_history [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_task_status._check_any_permission")
    async def test_get_task_status_no_history(
        self, mock_check,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_task_status import handle_get_task_status

        task_manager.get_tasks.return_value = []

        text, task_id, actions = await handle_get_task_status(
            intent, current_user, task_manager, config_manager, db
        )
        assert "пока нет задач" in text
        assert task_id is None
    # #endregion test_get_task_status_no_history

    # #region test_get_task_status_not_found [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_task_status._check_any_permission")
    async def test_get_task_status_not_found(
        self, mock_check,
        current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_task_status import handle_get_task_status

        task_manager.get_task.return_value = None

        with pytest.raises(HTTPException) as exc:
            await handle_get_task_status(
                {"entities": {"task_id": "nonexistent"}}, current_user, task_manager, config_manager, db
            )
        assert exc.value.status_code == 404
    # #endregion test_get_task_status_not_found


# ── _tool_llm_validation.py ───────────────────────────────────────────────────


class TestToolLlmValidation:
    """handle_run_llm_validation — create and run LLM validation."""

    # #region test_run_llm_validation_success [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_llm_validation.ValidationTaskService")
    @patch("src.api.routes.assistant._tool_llm_validation._check_any_permission")
    @patch("src.api.routes.assistant._tool_llm_validation._resolve_dashboard_id_entity")
    @patch("src.api.routes.assistant._tool_llm_validation._resolve_env_id")
    @patch("src.api.routes.assistant._tool_llm_validation._resolve_provider_id")
    async def test_run_llm_validation_success(
        self, mock_provider, mock_env, mock_resolve, mock_check,
        mock_validation_svc,
        task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_llm_validation import handle_run_llm_validation

        mock_env.return_value = "env-dev"
        mock_resolve.return_value = 42
        mock_provider.return_value = "provider-1"

        svc = MagicMock()
        mock_validation_svc.return_value = svc
        svc.create_task = AsyncMock(return_value=MagicMock(id="vt-1", name="Validation Task"))
        svc.trigger_run = AsyncMock(return_value={"task_id": "task-42", "run_id": "run-1"})

        intent_data = {
            "entities": {
                "dashboard_ref": "my-dashboard",
                "environment": "dev",
                "provider": "openai",
            }
        }
        text, task_id, actions = await handle_run_llm_validation(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "LLM-валидация запущена" in text
        assert task_id == "task-42"
        assert len(actions) == 2
    # #endregion test_run_llm_validation_success

    # #region test_run_llm_validation_missing_params [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_llm_validation._check_any_permission", return_value=True)
    async def test_run_llm_validation_missing_params(
        self, mock_perm, task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_llm_validation import handle_run_llm_validation

        with pytest.raises(HTTPException) as exc:
            await handle_run_llm_validation(
                {"entities": {}}, current_user, task_manager, config_manager, db
            )
        assert exc.value.status_code == 422
    # #endregion test_run_llm_validation_missing_params

    # #region test_run_llm_validation_create_task_error [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_llm_validation.ValidationTaskService")
    @patch("src.api.routes.assistant._tool_llm_validation._check_any_permission")
    @patch("src.api.routes.assistant._tool_llm_validation._resolve_dashboard_id_entity")
    @patch("src.api.routes.assistant._tool_llm_validation._resolve_env_id")
    @patch("src.api.routes.assistant._tool_llm_validation._resolve_provider_id")
    async def test_run_llm_validation_create_task_error(
        self, mock_provider, mock_env, mock_resolve, mock_check,
        mock_validation_svc,
        task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_llm_validation import handle_run_llm_validation

        mock_env.return_value = "env-dev"
        mock_resolve.return_value = 42
        mock_provider.return_value = "provider-1"

        svc = MagicMock()
        mock_validation_svc.return_value = svc
        svc.create_task = AsyncMock(side_effect=ValueError("Invalid config"))

        with pytest.raises(HTTPException) as exc:
            await handle_run_llm_validation(
                {"entities": {"dashboard_ref": "d", "environment": "dev", "provider": "openai"}},
                current_user, task_manager, config_manager, db
            )
        assert exc.value.status_code == 422
    # #endregion test_run_llm_validation_create_task_error

    # #region test_run_llm_validation_trigger_run_error [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_llm_validation.ValidationTaskService")
    @patch("src.api.routes.assistant._tool_llm_validation._check_any_permission")
    @patch("src.api.routes.assistant._tool_llm_validation._resolve_dashboard_id_entity")
    @patch("src.api.routes.assistant._tool_llm_validation._resolve_env_id")
    @patch("src.api.routes.assistant._tool_llm_validation._resolve_provider_id")
    async def test_run_llm_validation_trigger_run_error(
        self, mock_provider, mock_env, mock_resolve, mock_check,
        mock_validation_svc,
        task_manager, current_user, config_manager, db,
    ):
        from src.api.routes.assistant._tool_llm_validation import handle_run_llm_validation

        mock_env.return_value = "env-dev"
        mock_resolve.return_value = 42
        mock_provider.return_value = "provider-1"

        svc = MagicMock()
        mock_validation_svc.return_value = svc
        svc.create_task = AsyncMock(return_value=MagicMock(id="vt-1", name="Validation Task"))
        svc.trigger_run = AsyncMock(side_effect=ValueError("Trigger failed"))

        with pytest.raises(HTTPException) as exc:
            await handle_run_llm_validation(
                {"entities": {"dashboard_ref": "d", "environment": "dev", "provider": "openai"}},
                current_user, task_manager, config_manager, db
            )
        assert exc.value.status_code == 422
    # #endregion test_run_llm_validation_trigger_run_error


# ── _tool_search_dashboards.py ────────────────────────────────────────────────


class TestToolSearchDashboards:
    """handle_search_dashboards — search and list dashboards."""

    # #region test_search_dashboards_success [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_search_dashboards.SupersetClient")
    @patch("src.api.routes.assistant._tool_search_dashboards._resolve_env_id")
    async def test_search_dashboards_success(
        self, mock_env, mock_client_cls,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_search_dashboards import handle_search_dashboards

        mock_env.return_value = "env-dev"

        env_obj = _make_env("env-dev", "Development")
        config_manager.get_environments.return_value = [env_obj]

        client = MagicMock()
        mock_client_cls.return_value = client
        client.get_dashboards_summary = AsyncMock(return_value=[
            {"id": 1, "title": "Dashboard One", "slug": "dash-one"},
            {"id": 2, "title": "Dashboard Two", "slug": "dash-two"},
        ])

        intent_data = {"entities": {"environment": "dev"}}
        text, task_id, actions = await handle_search_dashboards(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "Дашборды" in text
        assert "Dashboard One" in text
        assert task_id is None
        assert len(actions) == 1
    # #endregion test_search_dashboards_success

    # #region test_search_dashboards_with_search [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_search_dashboards.SupersetClient")
    @patch("src.api.routes.assistant._tool_search_dashboards._resolve_env_id")
    async def test_search_dashboards_with_search(
        self, mock_env, mock_client_cls,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_search_dashboards import handle_search_dashboards

        mock_env.return_value = "env-dev"

        env_obj = _make_env("env-dev", "Development")
        config_manager.get_environments.return_value = [env_obj]

        client = MagicMock()
        mock_client_cls.return_value = client
        client.get_dashboards_summary = AsyncMock(return_value=[
            {"id": 1, "title": "Dashboard One", "slug": "dash-one"},
            {"id": 2, "title": "Dashboard Two", "slug": "dash-two"},
        ])

        intent_data = {"entities": {"environment": "dev", "search": "One"}}
        text, task_id, actions = await handle_search_dashboards(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "Dashboard One" in text
        assert "Dashboard Two" not in text  # filtered by search
    # #endregion test_search_dashboards_with_search

    # #region test_search_dashboards_no_env [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_search_dashboards._resolve_env_id")
    async def test_search_dashboards_no_env(
        self, mock_env,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_search_dashboards import handle_search_dashboards

        mock_env.return_value = None

        env1 = MagicMock(id="dev", name="Development")
        config_manager.get_environments.return_value = [env1]

        text, task_id, actions = await handle_search_dashboards(
            intent, current_user, task_manager, config_manager, db
        )
        assert "Укажите окружение" in text
    # #endregion test_search_dashboards_no_env

    # #region test_search_dashboards_no_envs_at_all [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_search_dashboards._resolve_env_id")
    async def test_search_dashboards_no_envs_at_all(
        self, mock_env,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_search_dashboards import handle_search_dashboards

        mock_env.return_value = None

        config_manager.get_environments.return_value = []

        text, task_id, actions = await handle_search_dashboards(
            intent, current_user, task_manager, config_manager, db
        )
        assert "Нет доступных окружений" in text
    # #endregion test_search_dashboards_no_envs_at_all

    # #region test_search_dashboards_env_not_found [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_search_dashboards._resolve_env_id")
    async def test_search_dashboards_env_not_found(
        self, mock_env,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_search_dashboards import handle_search_dashboards

        mock_env.return_value = "env-unknown"

        config_manager.get_environments.return_value = [MagicMock(id="dev", name="Dev")]

        intent_data = {"entities": {"environment": "unknown"}}
        text, task_id, actions = await handle_search_dashboards(
            intent_data, current_user, task_manager, config_manager, db
        )
        assert "не найдено" in text
    # #endregion test_search_dashboards_env_not_found


# ── _tool_llm.py ──────────────────────────────────────────────────────────────


class TestToolLlm:
    """handle_list_llm_providers and handle_get_llm_status."""

    # #region test_list_llm_providers [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_llm.LLMProviderService")
    async def test_list_llm_providers(
        self, mock_svc_cls,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_llm import handle_list_llm_providers

        svc = MagicMock()
        mock_svc_cls.return_value = svc
        p1 = MagicMock(name="OpenAI", provider_type="openai", is_active=True, default_model="gpt-4")
        p2 = MagicMock(name="Anthropic", provider_type="anthropic", is_active=False, default_model=None)
        svc.get_all_providers.return_value = [p1, p2]

        text, task_id, actions = await handle_list_llm_providers(
            intent, current_user, task_manager, config_manager, db
        )
        assert "LLM провайдеры" in text
        assert "OpenAI" in text
        assert "Anthropic" in text
        assert task_id is None
        assert len(actions) == 1
    # #endregion test_list_llm_providers

    # #region test_list_llm_providers_empty [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_llm.LLMProviderService")
    async def test_list_llm_providers_empty(
        self, mock_svc_cls,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_llm import handle_list_llm_providers

        svc = MagicMock()
        mock_svc_cls.return_value = svc
        svc.get_all_providers.return_value = []

        text, task_id, actions = await handle_list_llm_providers(
            intent, current_user, task_manager, config_manager, db
        )
        assert "не настроены" in text
    # #endregion test_list_llm_providers_empty

    # #region test_get_llm_status [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_llm.LLMProviderService")
    async def test_get_llm_status(
        self, mock_svc_cls,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_llm import handle_get_llm_status

        svc = MagicMock()
        mock_svc_cls.return_value = svc
        p1 = MagicMock(id="p1", name="OpenAI", provider_type="openai", is_active=True, default_model="gpt-4")
        svc.get_all_providers.return_value = [p1]
        svc.get_decrypted_api_key.return_value = "sk-valid-key"

        with patch("src.api.routes.llm._is_valid_runtime_api_key", return_value=True):
            text, task_id, actions = await handle_get_llm_status(
                intent, current_user, task_manager, config_manager, db
            )
        assert "LLM Статус" in text
        assert "OpenAI" in text
        assert "готов" in text
        assert task_id is None
    # #endregion test_get_llm_status

    # #region test_get_llm_status_no_providers [C:2] [TYPE Function]
    @patch("src.api.routes.assistant._tool_llm.LLMProviderService")
    async def test_get_llm_status_no_providers(
        self, mock_svc_cls,
        intent, current_user, task_manager, config_manager, db,
    ):
        from src.api.routes.assistant._tool_llm import handle_get_llm_status

        svc = MagicMock()
        mock_svc_cls.return_value = svc
        svc.get_all_providers.return_value = []

        text, task_id, actions = await handle_get_llm_status(
            intent, current_user, task_manager, config_manager, db
        )
        assert "не настроены" in text
    # #endregion test_get_llm_status_no_providers


# ── _tool_registry.py ─────────────────────────────────────────────────────────


class TestToolRegistry:
    """AssistantTool registry, dispatch, permissions, and catalog."""

    def _clear_registry(self):
        """Clear the global _tools registry."""
        import src.api.routes.assistant._tool_registry as reg
        reg._tools.clear()

    # #region test_assistant_tool_decorator [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_assistant_tool_decorator(self):
        from src.api.routes.assistant._tool_registry import assistant_tool, _tools

        self._clear_registry()

        @assistant_tool(
            operation="test_op",
            domain="test",
            description="Test tool",
            risk_level="safe",
            requires_confirmation=False,
        )
        async def my_handler(intent, user, tm, cm, db):
            return ("done", None, [])

        assert "test_op" in _tools
        tool = _tools["test_op"]
        assert tool.operation == "test_op"
        assert tool.domain == "test"
        assert tool.description == "Test tool"
        assert tool.risk_level == "safe"
        assert tool.requires_confirmation is False
        assert hasattr(my_handler, "__assistant_tool__")

        text, tid, acts = await _tools["test_op"].handler({}, None, None, None, None)
        assert text == "done"
    # #endregion test_assistant_tool_decorator

    # #region test_dispatch_supported_op [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_dispatch_supported_op(self):
        from src.api.routes.assistant._tool_registry import assistant_tool, dispatch, _tools
        self._clear_registry()

        @assistant_tool(operation="my_op", domain="t", description="t")
        async def my_handler(intent, user, tm, cm, db):
            return ("result", "task-1", [])

        text, tid, acts = await dispatch("my_op", {}, None, None, None, None)
        assert text == "result"
        assert tid == "task-1"
    # #endregion test_dispatch_supported_op

    # #region test_dispatch_unsupported_op [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_dispatch_unsupported_op(self):
        from src.api.routes.assistant._tool_registry import dispatch
        self._clear_registry()

        with pytest.raises(HTTPException) as exc:
            await dispatch("nonexistent", {}, None, None, None, None)
        assert exc.value.status_code == 400
    # #endregion test_dispatch_unsupported_op

    # #region test_get_safe_ops [C:2] [TYPE Function]
    def test_get_safe_ops(self):
        from src.api.routes.assistant._tool_registry import assistant_tool, get_safe_ops, _tools
        self._clear_registry()

        @assistant_tool(operation="safe_op", domain="t", description="t", risk_level="safe")
        async def h1(i, u, t, c, d): return ("", None, [])

        @assistant_tool(operation="guarded_op", domain="t", description="t", risk_level="guarded")
        async def h2(i, u, t, c, d): return ("", None, [])

        safe_ops = get_safe_ops()
        assert "safe_op" in safe_ops
        assert "guarded_op" not in safe_ops
        assert "dataset_review_answer_context" in safe_ops  # hardcoded safe op
    # #endregion test_get_safe_ops

    # #region test_get_catalog [C:2] [TYPE Function]
    def test_get_catalog(self, current_user, config_manager, db):
        from src.api.routes.assistant._tool_registry import assistant_tool, get_catalog, _tools
        self._clear_registry()

        @assistant_tool(operation="op1", domain="d1", description="desc1", risk_level="safe")
        async def h1(i, u, t, c, d): return ("", None, [])

        @assistant_tool(operation="op2", domain="d2", description="desc2", risk_level="guarded", requires_confirmation=True)
        async def h2(i, u, t, c, d): return ("", None, [])

        catalog = get_catalog(current_user, config_manager, db)
        ops = [e["operation"] for e in catalog]
        assert "op1" in ops
        assert "op2" in ops
        entry = next(e for e in catalog if e["operation"] == "op2")
        assert entry["risk_level"] == "guarded"
        assert entry["requires_confirmation"] is True
    # #endregion test_get_catalog

    # #region test_get_permission_checks [C:2] [TYPE Function]
    def test_get_permission_checks(self):
        from src.api.routes.assistant._tool_registry import assistant_tool, get_permission_checks, _tools
        self._clear_registry()

        @assistant_tool(operation="perm_op", domain="t", description="t",
                        permission_checks=[("resource", "action")])
        async def h1(i, u, t, c, d): return ("", None, [])

        @assistant_tool(operation="no_perm_op", domain="t", description="t")
        async def h2(i, u, t, c, d): return ("", None, [])

        checks = get_permission_checks()
        assert "perm_op" in checks
        assert checks["perm_op"] == [("resource", "action")]
        assert "no_perm_op" not in checks
    # #endregion test_get_permission_checks

    # #region test_check_any_permission_allows_valid [C:2] [TYPE Function]
    def test_check_any_permission_allows_valid(self, current_user):
        from src.api.routes.assistant._tool_registry import _check_any_permission

        with patch("src.api.routes.assistant._tool_registry.has_permission") as mock_hp:
            mock_hp.return_value = lambda u: None
            _check_any_permission(current_user, [("resource", "action")])
            mock_hp.assert_called_with("resource", "action")
    # #endregion test_check_any_permission_allows_valid

    # #region test_check_any_permission_denies_all [C:2] [TYPE Function]
    def test_check_any_permission_denies_all(self, current_user):
        from src.api.routes.assistant._tool_registry import _check_any_permission

        with patch("src.api.routes.assistant._tool_registry.has_permission") as mock_hp:
            mock_hp.return_value = lambda u: (_ for _ in ()).throw(HTTPException(status_code=403))

            with pytest.raises(HTTPException) as exc:
                _check_any_permission(current_user, [("r1", "a1"), ("r2", "a2")])
            assert exc.value.status_code == 403
    # #endregion test_check_any_permission_denies_all

    # #region test_has_any_permission [C:2] [TYPE Function]
    def test_has_any_permission(self, current_user):
        from src.api.routes.assistant._tool_registry import _has_any_permission

        with patch("src.api.routes.assistant._tool_registry.has_permission") as mock_hp:
            mock_hp.return_value = lambda u: None
            assert _has_any_permission(current_user, [("r", "a")]) is True

            mock_hp.return_value = lambda u: (_ for _ in ()).throw(HTTPException(status_code=403))
            assert _has_any_permission(current_user, [("r", "a")]) is False
    # #endregion test_has_any_permission


# ── _llm_planner.py ───────────────────────────────────────────────────────────


class TestLLMPlanner:
    """_build_tool_catalog and _coerce_intent_entities."""

    # #region test_build_tool_catalog_no_review [C:2] [TYPE Function]
    def test_build_tool_catalog_no_review(self, current_user, config_manager, db):
        from src.api.routes.assistant._llm_planner import _build_tool_catalog

        with patch("src.api.routes.assistant._llm_planner.get_catalog") as mock_cat:
            mock_cat.return_value = [{"operation": "op1", "domain": "d"}]
            result = _build_tool_catalog(current_user, config_manager, db)
            assert len(result) == 1
            assert result[0]["operation"] == "op1"
    # #endregion test_build_tool_catalog_no_review

    # #region test_build_tool_catalog_with_review [C:2] [TYPE Function]
    def test_build_tool_catalog_with_review(self, current_user, config_manager, db):
        from src.api.routes.assistant._llm_planner import _build_tool_catalog

        with patch("src.api.routes.assistant._llm_planner.get_catalog") as mock_cat:
            mock_cat.return_value = []
            with patch("src.api.routes.assistant._llm_planner._has_any_permission", return_value=True):
                result = _build_tool_catalog(
                    current_user, config_manager, db,
                    dataset_review_context={"session_id": "sess-1"},
                )
                assert len(result) == 4  # 4 dataset review tools
                ops = [t["operation"] for t in result]
                assert "dataset_review_answer_context" in ops
                assert "dataset_review_approve_mappings" in ops
    # #endregion test_build_tool_catalog_with_review

    # #region test_build_tool_catalog_with_review_filtered [C:2] [TYPE Function]
    def test_build_tool_catalog_with_review_filtered(self, current_user, config_manager, db):
        from src.api.routes.assistant._llm_planner import _build_tool_catalog

        with patch("src.api.routes.assistant._llm_planner.get_catalog") as mock_cat:
            mock_cat.return_value = []
            with patch("src.api.routes.assistant._llm_planner._has_any_permission", return_value=False):
                result = _build_tool_catalog(
                    current_user, config_manager, db,
                    dataset_review_context={"session_id": "sess-1"},
                )
                assert len(result) == 0
    # #endregion test_build_tool_catalog_with_review_filtered

    # #region test_coerce_intent_entities [C:2] [TYPE Function]
    def test_coerce_intent_entities(self):
        from src.api.routes.assistant._llm_planner import _coerce_intent_entities

        intent = {"entities": {"dashboard_id": "42", "dataset_id": "99", "branch_name": "  feature/test  "}}
        result = _coerce_intent_entities(intent)
        assert result["entities"]["dashboard_id"] == 42
        assert result["entities"]["dataset_id"] == 99
        assert result["entities"]["branch_name"] == "feature/test"
    # #endregion test_coerce_intent_entities

    # #region test_coerce_intent_entities_non_dict [C:2] [TYPE Function]
    def test_coerce_intent_entities_non_dict(self):
        from src.api.routes.assistant._llm_planner import _coerce_intent_entities

        intent = {"message": "hello"}
        result = _coerce_intent_entities(intent)
        assert result["entities"] == {}
    # #endregion test_coerce_intent_entities_non_dict


# ── _llm_planner_intent.py ───────────────────────────────────────────────────


class TestLLMPlannerIntent:
    """_plan_intent_with_llm and _authorize_intent."""

    # #region test_authorize_intent_passes [C:2] [TYPE Function]
    def test_authorize_intent_passes(self, current_user):
        from src.api.routes.assistant._llm_planner_intent import _authorize_intent

        with patch("src.api.routes.assistant._llm_planner_intent.get_permission_checks") as mock_checks:
            mock_checks.return_value = {"test_op": [("resource", "action")]}
            with patch("src.api.routes.assistant._llm_planner_intent._check_any_permission") as mock_check:
                _authorize_intent({"operation": "test_op"}, current_user)
                mock_check.assert_called_with(current_user, [("resource", "action")])
    # #endregion test_authorize_intent_passes

    # #region test_authorize_intent_no_checks [C:2] [TYPE Function]
    def test_authorize_intent_no_checks(self, current_user):
        from src.api.routes.assistant._llm_planner_intent import _authorize_intent

        with patch("src.api.routes.assistant._llm_planner_intent.get_permission_checks") as mock_checks:
            mock_checks.return_value = {}
            _authorize_intent({"operation": "safe_op"}, current_user)  # Should not raise
    # #endregion test_authorize_intent_no_checks


# ── _dispatch.py ──────────────────────────────────────────────────────────────


class TestAssistantDispatch:
    """_get_git_service and _clarification_text_for_intent."""

    # #region test_get_git_service [C:2] [TYPE Function]
    def test_get_git_service(self):
        from src.api.routes.assistant._dispatch import _get_git_service

        import src.api.routes.assistant._dispatch as dispatch_mod
        dispatch_mod._git_service_instance = None

        svc = _get_git_service()
        assert svc is not None

        svc2 = _get_git_service()
        assert svc2 is svc
    # #endregion test_get_git_service

    # #region test_clarification_text_for_intent [C:2] [TYPE Function]
    def test_clarification_text_for_intent(self):
        from src.api.routes.assistant._dispatch import _clarification_text_for_intent

        # Known operation
        text = _clarification_text_for_intent(
            {"operation": "create_branch"}, "Missing params"
        )
        assert "Нужно уточнение" in text
        assert "ветки" in text

        # Unknown operation — falls back to detail_text
        text = _clarification_text_for_intent(
            {"operation": "unknown_op"}, "Custom error detail"
        )
        assert text == "Custom error detail"

        # None intent
        text = _clarification_text_for_intent(None, "No intent")
        assert text == "No intent"
    # #endregion test_clarification_text_for_intent

    # #region test_dispatch_intent_wrapper [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_dispatch_intent_wrapper(self):
        from src.api.routes.assistant._dispatch import _dispatch_intent

        with patch("src.api.routes.assistant._dispatch._registry_dispatch") as mock_reg:
            mock_reg.return_value = ("done", "task-1", [])
            result = await _dispatch_intent(
                {"operation": "test"}, None, None, None, None
            )
            assert result == ("done", "task-1", [])
            mock_reg.assert_called_with("test", {"operation": "test"}, None, None, None, None)
    # #endregion test_dispatch_intent_wrapper


# ── _resolvers.py ─────────────────────────────────────────────────────────────


class TestAssistantResolvers:
    """_resolve_env_id, _is_production_env, _resolve_provider_id, etc."""

    # #region test_extract_id [C:2] [TYPE Function]
    def test_extract_id(self):
        from src.api.routes.assistant._resolvers import _extract_id

        result = _extract_id("my ref is abc123", [r"ref is (\w+)"])
        assert result == "abc123"

        result = _extract_id("no match", [r"nothing"])
        assert result is None
    # #endregion test_extract_id

    # #region test_resolve_env_id [C:2] [TYPE Function]
    def test_resolve_env_id(self):
        from src.api.routes.assistant._resolvers import _resolve_env_id

        config_manager = MagicMock()
        env1 = _make_env("env-dev", "Development")
        env2 = _make_env("env-prod", "Production")
        config_manager.get_environments = MagicMock(return_value=[env1, env2])

        assert _resolve_env_id("env-dev", config_manager) == "env-dev"
        assert _resolve_env_id("Development", config_manager) == "env-dev"
        assert _resolve_env_id(None, config_manager) is None
        assert _resolve_env_id("unknown", config_manager) is None
    # #endregion test_resolve_env_id

    # #region test_is_production_env [C:2] [TYPE Function]
    def test_is_production_env(self):
        from src.api.routes.assistant._resolvers import _is_production_env

        config_manager = MagicMock()
        env1 = _make_env("env-dev", "Development")
        env2 = _make_env("env-prod", "Production")
        config_manager.get_environments = MagicMock(return_value=[env1, env2])

        assert _is_production_env("env-prod", config_manager) is True
        assert _is_production_env("env-dev", config_manager) is False
        assert _is_production_env("prod", config_manager) is True
        assert _is_production_env("staging", config_manager) is False
        assert _is_production_env(None, config_manager) is False
    # #endregion test_is_production_env

    # #region test_resolve_provider_id_by_token [C:2] [TYPE Function]
    def test_resolve_provider_id_by_token(self):
        from src.api.routes.assistant._resolvers import _resolve_provider_id

        db = MagicMock()
        config_manager = MagicMock()
        p1 = MagicMock(id="p1", name="OpenAI")
        p2 = MagicMock(id="p2", name="Anthropic")

        with patch("src.api.routes.assistant._resolvers.LLMProviderService") as mock_svc_cls:
            svc = MagicMock()
            mock_svc_cls.return_value = svc
            svc.get_all_providers.return_value = [p1, p2]

            assert _resolve_provider_id("OpenAI", db) == "p1"
            assert _resolve_provider_id("p2", db) == "p2"
            assert _resolve_provider_id(None, db) == "p1"  # returns first active
    # #endregion test_resolve_provider_id_by_token

    # #region test_resolve_provider_id_no_providers [C:2] [TYPE Function]
    def test_resolve_provider_id_no_providers(self):
        from src.api.routes.assistant._resolvers import _resolve_provider_id

        db = MagicMock()
        with patch("src.api.routes.assistant._resolvers.LLMProviderService") as mock_svc_cls:
            svc = MagicMock()
            mock_svc_cls.return_value = svc
            svc.get_all_providers.return_value = []

            assert _resolve_provider_id(None, db) is None
    # #endregion test_resolve_provider_id_no_providers

    # #region test_get_default_environment_id [C:2] [TYPE Function]
    def test_get_default_environment_id(self):
        from src.api.routes.assistant._resolvers import _get_default_environment_id

        config_manager = MagicMock()
        env1 = _make_env("env-dev", "Dev", is_default=True)
        env2 = _make_env("env-prod", "Prod", is_default=False)
        config_manager.get_environments = MagicMock(return_value=[env1, env2])

        assert _get_default_environment_id(config_manager) == "env-dev"

        config_manager.get_environments = MagicMock(return_value=[])
        assert _get_default_environment_id(config_manager) is None
    # #endregion test_get_default_environment_id

    # #region test_get_environment_name_by_id [C:2] [TYPE Function]
    def test_get_environment_name_by_id(self):
        from src.api.routes.assistant._resolvers import _get_environment_name_by_id

        config_manager = MagicMock()
        env = _make_env("env-dev", "Development")
        config_manager.get_environments = MagicMock(return_value=[env])

        assert _get_environment_name_by_id("env-dev", config_manager) == "Development"
        assert _get_environment_name_by_id(None, config_manager) == "unknown"
        assert _get_environment_name_by_id("missing", config_manager) == "missing"
    # #endregion test_get_environment_name_by_id

    # #region test_resolve_dashboard_id_entity_int [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_resolve_dashboard_id_entity_int(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity

        config_manager = MagicMock()
        result = await _resolve_dashboard_id_entity(
            {"dashboard_id": 42}, config_manager
        )
        assert result == 42
    # #endregion test_resolve_dashboard_id_entity_int

    # #region test_resolve_dashboard_id_entity_str_digit [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_resolve_dashboard_id_entity_str_digit(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity

        config_manager = MagicMock()
        result = await _resolve_dashboard_id_entity(
            {"dashboard_id": "42"}, config_manager
        )
        assert result == 42
    # #endregion test_resolve_dashboard_id_entity_str_digit

    # #region test_resolve_dashboard_id_entity_ref_fallback [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_resolve_dashboard_id_entity_ref_fallback(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity

        config_manager = MagicMock()
        with patch("src.api.routes.assistant._resolvers._resolve_env_id", return_value="env-dev"):
            with patch("src.api.routes.assistant._resolvers._get_default_environment_id", return_value="env-dev"):
                with patch("src.api.routes.assistant._resolvers._resolve_dashboard_id_by_ref", return_value=99):
                    result = await _resolve_dashboard_id_entity(
                        {"dashboard_id": "my-slug"}, config_manager
                    )
                    assert result == 99
    # #endregion test_resolve_dashboard_id_entity_ref_fallback

    # #region test_resolve_dashboard_id_entity_no_match [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_resolve_dashboard_id_entity_no_match(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity

        config_manager = MagicMock()
        result = await _resolve_dashboard_id_entity({}, config_manager)
        assert result is None
    # #endregion test_resolve_dashboard_id_entity_no_match

    # #region test_resolve_dashboard_id_by_ref [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_resolve_dashboard_id_by_ref(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref

        config_manager = MagicMock()
        env = _make_env("env-dev", "Development")
        config_manager.get_environments = MagicMock(return_value=[env])

        with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_client_cls:
            client = MagicMock()
            mock_client_cls.return_value = client
            client.get_dashboards = AsyncMock(return_value=(None, [
                {"id": 1, "dashboard_title": "Dashboard", "slug": "dash-one"},
            ]))

            result = await _resolve_dashboard_id_by_ref("dash-one", "env-dev", config_manager)
            assert result == 1
    # #endregion test_resolve_dashboard_id_by_ref

    # #region test_resolve_dashboard_id_by_ref_no_env [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_resolve_dashboard_id_by_ref_no_env(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref

        config_manager = MagicMock()
        config_manager.get_environments = MagicMock(return_value=[])
        result = await _resolve_dashboard_id_by_ref("ref", "nonexistent", config_manager)
        assert result is None
    # #endregion test_resolve_dashboard_id_by_ref_no_env

    # #region test_resolve_dashboard_id_by_ref_no_ref [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_resolve_dashboard_id_by_ref_no_ref(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref

        config_manager = MagicMock()
        result = await _resolve_dashboard_id_by_ref(None, None, config_manager)
        assert result is None
    # #endregion test_resolve_dashboard_id_by_ref_no_ref

    # #region test_extract_result_deep_links_migration [C:2] [TYPE Function]
    def test_extract_result_deep_links_migration(self):
        from src.api.routes.assistant._resolvers import _extract_result_deep_links

        config_manager = MagicMock()
        env = _make_env("env-prod", "Production")
        config_manager.get_environments = MagicMock(return_value=[env])

        task = MagicMock(
            plugin_id="superset-migration",
            params={"selected_ids": [123], "target_env_id": "env-prod"},
            result={"migrated_dashboards": [{"id": 123}]},
        )
        actions = _extract_result_deep_links(task, config_manager)
        assert len(actions) >= 1
    # #endregion test_extract_result_deep_links_migration

    # #region test_extract_result_deep_links_backup [C:2] [TYPE Function]
    def test_extract_result_deep_links_backup(self):
        from src.api.routes.assistant._resolvers import _extract_result_deep_links

        config_manager = MagicMock()
        env = _make_env("env-dev", "Dev")
        config_manager.get_environments = MagicMock(return_value=[env])

        task = MagicMock(
            plugin_id="superset-backup",
            params={"dashboard_ids": [456], "environment_id": "env-dev"},
            result={"dashboards": [{"id": 456}]},
        )
        actions = _extract_result_deep_links(task, config_manager)
        assert len(actions) >= 1
    # #endregion test_extract_result_deep_links_backup

    # #region test_extract_result_deep_links_validation [C:2] [TYPE Function]
    def test_extract_result_deep_links_validation(self):
        from src.api.routes.assistant._resolvers import _extract_result_deep_links

        config_manager = MagicMock()
        env = _make_env("env-dev", "Dev")
        config_manager.get_environments = MagicMock(return_value=[env])

        task = MagicMock(
            plugin_id="llm_dashboard_validation",
            params={"dashboard_id": 789, "environment_id": "env-dev"},
            result={},
        )
        actions = _extract_result_deep_links(task, config_manager)
        assert len(actions) >= 1
    # #endregion test_extract_result_deep_links_validation

    # #region test_extract_result_deep_links_no_match [C:2] [TYPE Function]
    def test_extract_result_deep_links_no_match(self):
        from src.api.routes.assistant._resolvers import _extract_result_deep_links

        config_manager = MagicMock()
        task = MagicMock(
            plugin_id="unknown_plugin",
            params={},
            result={},
        )
        actions = _extract_result_deep_links(task, config_manager)
        assert actions == []
    # #endregion test_extract_result_deep_links_no_match

    # #region test_build_task_observability_summary_migration [C:2] [TYPE Function]
    def test_build_task_observability_summary_migration(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary

        config_manager = MagicMock()
        env = _make_env("env-prod", "Production")
        config_manager.get_environments = MagicMock(return_value=[env])

        task = MagicMock(
            plugin_id="superset-migration",
            status="SUCCESS",
            params={"target_env_id": "env-prod"},
            result={
                "migrated_dashboards": [{"id": 1}],
                "selected_dashboards": 5,
                "mapping_count": 10,
                "failed_dashboards": [],
            },
        )
        summary = _build_task_observability_summary(task, config_manager)
        assert "Сводка миграции" in summary
        assert "5" in summary
    # #endregion test_build_task_observability_summary_migration

    # #region test_build_task_observability_summary_migration_with_errors [C:2] [TYPE Function]
    def test_build_task_observability_summary_migration_with_errors(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary

        config_manager = MagicMock()
        env = _make_env("env-prod", "Production")
        config_manager.get_environments = MagicMock(return_value=[env])

        task = MagicMock(
            plugin_id="superset-migration",
            status="FAILED",
            params={"target_env_id": "env-prod"},
            result={
                "migrated_dashboards": [{"id": 1}],
                "selected_dashboards": 3,
                "mapping_count": 5,
                "failed_dashboards": [
                    {"id": 2, "title": "BrokenDash", "error": "timeout"}
                ],
            },
        )
        summary = _build_task_observability_summary(task, config_manager)
        assert "с ошибками" in summary
        assert "Внимание" in summary
    # #endregion test_build_task_observability_summary_migration_with_errors

    # #region test_build_task_observability_summary_backup [C:2] [TYPE Function]
    def test_build_task_observability_summary_backup(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary

        config_manager = MagicMock()
        env = _make_env("env-dev", "Dev")
        config_manager.get_environments = MagicMock(return_value=[env])

        task = MagicMock(
            plugin_id="superset-backup",
            status="SUCCESS",
            params={"environment_id": "env-dev"},
            result={
                "total_dashboards": 10,
                "backed_up_dashboards": 8,
                "failed_dashboards": 2,
                "failures": [{"id": 3, "title": "Broken", "error": "err"}],
            },
        )
        summary = _build_task_observability_summary(task, config_manager)
        assert "Сводка бэкапа" in summary
        assert "8" in summary
    # #endregion test_build_task_observability_summary_backup

    # #region test_build_task_observability_summary_validation [C:2] [TYPE Function]
    def test_build_task_observability_summary_validation(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary

        config_manager = MagicMock()
        task = MagicMock(
            plugin_id="llm_dashboard_validation",
            status="SUCCESS",
            params={},
            result={
                "status": "ALL_PASSED",
                "summary": "All checks passed",
                "issues": [],
            },
        )
        summary = _build_task_observability_summary(task, config_manager)
        assert "Сводка валидации" in summary
    # #endregion test_build_task_observability_summary_validation

    # #region test_build_task_observability_summary_fallback [C:2] [TYPE Function]
    def test_build_task_observability_summary_fallback(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary

        config_manager = MagicMock()
        task = MagicMock(
            plugin_id="unknown",
            status="SUCCESS",
            params={},
            result={},
        )
        summary = _build_task_observability_summary(task, config_manager)
        assert "Задача завершена" in summary

        task = MagicMock(
            plugin_id="unknown",
            status="RUNNING",
            params={},
            result={},
        )
        summary = _build_task_observability_summary(task, config_manager)
        assert summary == ""
    # #endregion test_build_task_observability_summary_fallback


# ── _routes.py ────────────────────────────────────────────────────────────────


class TestAssistantRoutes:
    """send_message route — the main assistant entry point."""

    # #region test_send_message_clarification [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_send_message_clarification(self):
        from src.api.routes.assistant._routes import send_message
        from src.api.routes.assistant._schemas import AssistantMessageRequest, CONFIRMATIONS

        CONFIRMATIONS.clear()

        req = AssistantMessageRequest(message="do something vague")

        with patch("src.api.routes.assistant._routes._resolve_or_create_conversation", return_value="conv-1"):
            with patch("src.api.routes.assistant._routes._append_history"):
                with patch("src.api.routes.assistant._routes._persist_message"):
                    with patch("src.api.routes.assistant._routes._build_tool_catalog", return_value=[]):
                        with patch("src.api.routes.assistant._routes._plan_intent_with_llm", return_value=None):
                            with patch("src.api.routes.assistant._routes._parse_command", return_value={"domain": "unknown", "operation": "clarify", "entities": {}, "confidence": 0.3}):
                                with patch("src.api.routes.assistant._routes._audit"):
                                    with patch("src.api.routes.assistant._routes._persist_audit"):
                                        with patch("src.api.routes.assistant._routes._load_dataset_review_context", return_value=None):
                                            current_user = MagicMock(id="user-1")
                                            task_manager = MagicMock()
                                            config_manager = MagicMock()
                                            db = MagicMock()

                                            resp = await send_message(
                                                req, current_user, task_manager, config_manager, db
                                            )
                                            assert resp.state == "needs_clarification"
                                            assert resp.intent is not None
                                            assert len(resp.actions) > 0
    # #endregion test_send_message_clarification

    # #region test_send_message_safe_operation [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_send_message_safe_operation(self):
        from src.api.routes.assistant._routes import send_message
        from src.api.routes.assistant._schemas import AssistantMessageRequest, CONFIRMATIONS
        from src.api.routes.assistant._tool_registry import assistant_tool, _tools

        CONFIRMATIONS.clear()
        _tools.clear()

        @assistant_tool(operation="show_capabilities", domain="assistant", description="Show capabilities", risk_level="safe")
        async def caps(intent, user, tm, cm, db):
            return ("Here are capabilities", None, [])

        req = AssistantMessageRequest(message="what can you do")

        with patch("src.api.routes.assistant._routes._resolve_or_create_conversation", return_value="conv-1"):
            with patch("src.api.routes.assistant._routes._append_history"):
                with patch("src.api.routes.assistant._routes._persist_message"):
                    with patch("src.api.routes.assistant._routes._build_tool_catalog", return_value=[{"operation": "show_capabilities", "domain": "assistant", "description": "Show caps"}]):
                        with patch("src.api.routes.assistant._routes._plan_intent_with_llm", return_value={"domain": "assistant", "operation": "show_capabilities", "entities": {}, "confidence": 0.9, "risk_level": "safe", "requires_confirmation": False}):
                            with patch("src.api.routes.assistant._routes._audit"):
                                with patch("src.api.routes.assistant._routes._persist_audit"):
                                    with patch("src.api.routes.assistant._routes._load_dataset_review_context", return_value=None):
                                        current_user = MagicMock(id="user-1")
                                        task_manager = MagicMock()
                                        config_manager = MagicMock()
                                        db = MagicMock()

                                        resp = await send_message(
                                            req, current_user, task_manager, config_manager, db
                                        )
                                        assert resp.state in ("success", "started")
                                        assert "capabilities" in resp.text.lower()
    # #endregion test_send_message_safe_operation

    # #region test_send_message_guarded_needs_confirmation [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_send_message_guarded_needs_confirmation(self):
        from src.api.routes.assistant._routes import send_message
        from src.api.routes.assistant._schemas import AssistantMessageRequest, CONFIRMATIONS
        from src.api.routes.assistant._tool_registry import _tools

        CONFIRMATIONS.clear()
        _tools.clear()

        req = AssistantMessageRequest(message="deploy dashboard 42 to prod")

        with patch("src.api.routes.assistant._routes._resolve_or_create_conversation", return_value="conv-1"):
            with patch("src.api.routes.assistant._routes._append_history"):
                with patch("src.api.routes.assistant._routes._persist_message"):
                    with patch("src.api.routes.assistant._routes._build_tool_catalog", return_value=[{"operation": "deploy_dashboard", "domain": "git", "description": "Deploy"}]):
                        with patch("src.api.routes.assistant._routes._plan_intent_with_llm", return_value={"domain": "git", "operation": "deploy_dashboard", "entities": {"environment": "prod", "dashboard_id": 42}, "confidence": 0.9, "risk_level": "guarded", "requires_confirmation": True}):
                            with patch("src.api.routes.assistant._routes._async_confirmation_summary", return_value="Confirm deploy?"):
                                with patch("src.api.routes.assistant._routes._audit"):
                                    with patch("src.api.routes.assistant._routes._persist_audit"):
                                        with patch("src.api.routes.assistant._routes._load_dataset_review_context", return_value=None):
                                            current_user = MagicMock(id="user-1")
                                            task_manager = MagicMock()
                                            config_manager = MagicMock()
                                            db = MagicMock()

                                            resp = await send_message(
                                                req, current_user, task_manager, config_manager, db
                                            )
                                            assert resp.state == "needs_confirmation"
                                            assert resp.confirmation_id is not None
                                            assert len(resp.actions) > 1
    # #endregion test_send_message_guarded_needs_confirmation

    # #region test_send_message_dataset_review_override [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_send_message_dataset_review_override(self):
        from src.api.routes.assistant._routes import send_message
        from src.api.routes.assistant._schemas import AssistantMessageRequest, CONFIRMATIONS
        from src.api.routes.assistant._tool_registry import _tools

        CONFIRMATIONS.clear()
        _tools.clear()

        req = AssistantMessageRequest(message="review dataset", dataset_review_session_id="sess-1")

        with patch("src.api.routes.assistant._routes._resolve_or_create_conversation", return_value="conv-1"):
            with patch("src.api.routes.assistant._routes._append_history"):
                with patch("src.api.routes.assistant._routes._persist_message"):
                    with patch("src.api.routes.assistant._routes._build_tool_catalog", return_value=[{"operation": "dataset_review_answer_context", "domain": "dataset_review"}]):
                        with patch("src.api.routes.assistant._routes._plan_intent_with_llm", return_value={"domain": "dataset_review", "operation": "dataset_review_answer_context", "entities": {}, "confidence": 0.9, "risk_level": "safe", "requires_confirmation": False}):
                            with patch("src.api.routes.assistant._routes._parse_command", return_value={"domain": "unknown", "operation": "clarify", "entities": {}, "confidence": 0.3}):
                                with patch("src.api.routes.assistant._routes._plan_dataset_review_intent", return_value=None):
                                    with patch("src.api.routes.assistant._routes._authorize_intent"):
                                            with patch("src.api.routes.assistant._routes.get_safe_ops", return_value={"dataset_review_answer_context"}):
                                                with patch("src.api.routes.assistant._routes.dispatch",
                                                           return_value=("Review started", "task-1", [])):
                                                            with patch("src.api.routes.assistant._routes._audit"):
                                                                with patch("src.api.routes.assistant._routes._persist_audit"):
                                                                    with patch("src.api.routes.assistant._routes._load_dataset_review_context", return_value={"session_id": "sess-1"}):
                                                                        current_user = MagicMock(id="user-1")
                                                                        task_manager = MagicMock()
                                                                        config_manager = MagicMock()
                                                                        db = MagicMock()

                                                                        resp = await send_message(
                                                                            req, current_user, task_manager, config_manager, db
                                                                        )
                                                                        assert resp.state in ("success", "started")
    # #endregion test_send_message_dataset_review_override

    # #region test_send_message_http_exception_denied [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_send_message_http_exception_denied(self):
        from src.api.routes.assistant._routes import send_message
        from src.api.routes.assistant._schemas import AssistantMessageRequest, CONFIRMATIONS
        from src.api.routes.assistant._tool_registry import _tools

        CONFIRMATIONS.clear()
        _tools.clear()

        req = AssistantMessageRequest(message="something")

        with patch("src.api.routes.assistant._routes._resolve_or_create_conversation", return_value="conv-1"):
            with patch("src.api.routes.assistant._routes._append_history"):
                with patch("src.api.routes.assistant._routes._persist_message"):
                    with patch("src.api.routes.assistant._routes._build_tool_catalog", return_value=[{"operation": "guarded_op"}]):
                        with patch("src.api.routes.assistant._routes._plan_intent_with_llm", return_value={"domain": "d", "operation": "guarded_op", "entities": {}, "confidence": 0.9, "risk_level": "guarded", "requires_confirmation": False}):
                            with patch("src.api.routes.assistant._routes._authorize_intent", side_effect=HTTPException(status_code=403, detail="Forbidden")):
                                with patch("src.api.routes.assistant._routes._audit"):
                                    with patch("src.api.routes.assistant._routes._persist_audit"):
                                        with patch("src.api.routes.assistant._routes._load_dataset_review_context", return_value=None):
                                            current_user = MagicMock(id="user-1")
                                            task_manager = MagicMock()
                                            config_manager = MagicMock()
                                            db = MagicMock()

                                            resp = await send_message(
                                                req, current_user, task_manager, config_manager, db
                                            )
                                            assert resp.state == "denied"
    # #endregion test_send_message_http_exception_denied

    # #region test_send_message_http_exception_clarification [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_send_message_http_exception_clarification(self):
        from src.api.routes.assistant._routes import send_message
        from src.api.routes.assistant._schemas import AssistantMessageRequest, CONFIRMATIONS
        from src.api.routes.assistant._tool_registry import _tools

        CONFIRMATIONS.clear()
        _tools.clear()

        req = AssistantMessageRequest(message="do something")

        with patch("src.api.routes.assistant._routes._resolve_or_create_conversation", return_value="conv-1"):
            with patch("src.api.routes.assistant._routes._append_history"):
                with patch("src.api.routes.assistant._routes._persist_message"):
                    with patch("src.api.routes.assistant._routes._build_tool_catalog", return_value=[]):
                        with patch("src.api.routes.assistant._routes._plan_intent_with_llm", return_value={"domain": "d", "operation": "commit_changes", "entities": {}, "confidence": 0.9, "risk_level": "guarded", "requires_confirmation": False}):
                            with patch("src.api.routes.assistant._routes._authorize_intent"):
                                    with patch("src.api.routes.assistant._routes.get_safe_ops", return_value={"commit_changes"}):
                                            with patch("src.api.routes.assistant._routes.dispatch",
                                                        side_effect=HTTPException(status_code=422, detail="Missing dashboard_id")):
                                                    with patch("src.api.routes.assistant._routes._audit"):
                                                        with patch("src.api.routes.assistant._routes._persist_audit"):
                                                            with patch("src.api.routes.assistant._routes._load_dataset_review_context", return_value=None):
                                                                current_user = MagicMock(id="user-1")
                                                                task_manager = MagicMock()
                                                                config_manager = MagicMock()
                                                                db = MagicMock()

                                                                resp = await send_message(
                                                                    req, current_user, task_manager, config_manager, db
                                                                )
                                                                assert resp.state == "needs_clarification"
    # #endregion test_send_message_http_exception_clarification

    # #region test_send_message_planner_fallback [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_send_message_planner_fallback(self):
        from src.api.routes.assistant._routes import send_message
        from src.api.routes.assistant._schemas import AssistantMessageRequest, CONFIRMATIONS
        from src.api.routes.assistant._tool_registry import _tools

        CONFIRMATIONS.clear()
        _tools.clear()

        req = AssistantMessageRequest(message="show capabilities")

        with patch("src.api.routes.assistant._routes._resolve_or_create_conversation", return_value="conv-1"):
            with patch("src.api.routes.assistant._routes._append_history"):
                with patch("src.api.routes.assistant._routes._persist_message"):
                    with patch("src.api.routes.assistant._routes._build_tool_catalog", return_value=[{"operation": "show_capabilities", "domain": "assistant"}]):
                        with patch("src.api.routes.assistant._routes._plan_intent_with_llm", side_effect=Exception("LLM down")):
                                with patch("src.api.routes.assistant._routes._parse_command", return_value={"domain": "assistant", "operation": "show_capabilities", "entities": {}, "confidence": 0.9, "risk_level": "safe", "requires_confirmation": False}):
                                    with patch("src.api.routes.assistant._routes._authorize_intent"):
                                        with patch("src.api.routes.assistant._routes.get_safe_ops", return_value={"show_capabilities"}):
                                            with patch("src.api.routes.assistant._routes.dispatch",
                                                       return_value=("Capabilities list", None, [])):
                                                with patch("src.api.routes.assistant._routes._audit"):
                                                    with patch("src.api.routes.assistant._routes._persist_audit"):
                                                        with patch("src.api.routes.assistant._routes._load_dataset_review_context", return_value=None):
                                                            current_user = MagicMock(id="user-1")
                                                            task_manager = MagicMock()
                                                            config_manager = MagicMock()
                                                            db = MagicMock()

                                                            resp = await send_message(
                                                                req, current_user, task_manager, config_manager, db
                                                            )
                                                            # show_capabilities is safe — should dispatch without confirmation
                                                            assert resp.state in ("success", "started")
    # #endregion test_send_message_planner_fallback


# #endregion Test.Assistant.Tools
