# #region Test.Assistant.Resolvers [C:2] [TYPE Module] [SEMANTICS test,assistant,resolver,environment,dashboard]
# @BRIEF Tests for _resolvers.py — all environment, dashboard, provider resolution functions.
# @RELATION BINDS_TO -> [AssistantResolvers]
# @TEST_CONTRACT: _extract_id -> returns first regex match group or None
# @TEST_CONTRACT: _resolve_env_id -> matches by id or name, returns canonical id
# @TEST_CONTRACT: _is_production_env -> detects production via env metadata or token text
# @TEST_CONTRACT: _resolve_provider_id -> uses token match, bound provider, then active/default
# @TEST_CONTRACT: _get_default_environment_id -> uses settings.default, is_default flag, or first
# @TEST_CONTRACT: _resolve_dashboard_id_by_ref -> matches by slug, title, or partial title
# @TEST_CONTRACT: _resolve_dashboard_id_entity -> resolves via numeric id, string token, or ref
# @TEST_CONTRACT: _get_environment_name_by_id -> returns name or fallback
# @TEST_CONTRACT: _extract_result_deep_links -> builds actions from task result
# @TEST_CONTRACT: _build_task_observability_summary -> builds text summary per plugin type
# @TEST_EDGE: _resolve_provider_id_no_providers -> returns None
# @TEST_EDGE: _resolve_provider_id_bound_provider_not_in_list -> falls through to active
# @TEST_EDGE: _resolve_provider_id_bound_provider_config_error -> falls through gracefully
# @TEST_EDGE: _resolve_dashboard_id_by_ref_no_env -> returns None
# @TEST_EDGE: _resolve_dashboard_id_by_ref_superset_error -> returns None
# @TEST_EDGE: _resolve_dashboard_id_entity_int_dashboard_id -> returns as-is
# @TEST_EDGE: _resolve_dashboard_id_entity_string_digit -> converts to int
# @TEST_EDGE: _build_task_observability_summary_unknown_plugin -> returns empty for non-terminal
# @TEST_EDGE: _build_task_observability_summary_migration_with_empty_lists -> handles gracefully

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

import pytest

from src.schemas.auth import User


def _make_env(id_: str, name_: str, stage="DEV", is_production=False, is_default=False):
    env = MagicMock()
    env.id = id_
    env.name = name_
    env.stage = stage
    env.is_production = is_production
    env.is_default = is_default
    return env


class TestExtractId:
    """_extract_id — regex extraction."""

    def test_match_first_pattern(self):
        from src.api.routes.assistant._resolvers import _extract_id

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

    def test_match_second_pattern(self):
        from src.api.routes.assistant._resolvers import _extract_id

        result = _extract_id("id: 42", [r"nope", r"id: (\d+)"])
        assert result == "42"

    def test_no_match(self):
        from src.api.routes.assistant._resolvers import _extract_id

        result = _extract_id("nothing here", [r"ref is (\w+)"])
        assert result is None

    def test_case_insensitive(self):
        from src.api.routes.assistant._resolvers import _extract_id

        result = _extract_id("REF IS ABC123", [r"ref is (\w+)"])
        assert result == "ABC123"


class TestResolveEnvId:
    """_resolve_env_id — environment resolution."""

    def test_match_by_id(self):
        from src.api.routes.assistant._resolvers import _resolve_env_id
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Development")]

        assert _resolve_env_id("env-dev", cm) == "env-dev"

    def test_match_by_name(self):
        from src.api.routes.assistant._resolvers import _resolve_env_id
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Development")]

        assert _resolve_env_id("Development", cm) == "env-dev"

    def test_case_insensitive(self):
        from src.api.routes.assistant._resolvers import _resolve_env_id
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("ENV-PROD", "PRODUCTION")]

        assert _resolve_env_id("env-prod", cm) == "ENV-PROD"
        assert _resolve_env_id("production", cm) == "ENV-PROD"

    def test_none_token(self):
        from src.api.routes.assistant._resolvers import _resolve_env_id
        assert _resolve_env_id(None, MagicMock()) is None

    def test_unknown_token(self):
        from src.api.routes.assistant._resolvers import _resolve_env_id
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Development")]

        assert _resolve_env_id("unknown", cm) is None


class TestIsProductionEnv:
    """_is_production_env — production detection."""

    def test_token_text_prod(self):
        from src.api.routes.assistant._resolvers import _is_production_env
        assert _is_production_env("prod", MagicMock()) is True
        assert _is_production_env("PRODUCTION", MagicMock()) is True

    def test_token_text_non_prod(self):
        from src.api.routes.assistant._resolvers import _is_production_env
        assert _is_production_env("dev", MagicMock()) is False
        assert _is_production_env("staging", MagicMock()) is False

    def test_none_token(self):
        from src.api.routes.assistant._resolvers import _is_production_env
        assert _is_production_env(None, MagicMock()) is False

    def test_via_env_metadata(self):
        from src.api.routes.assistant._resolvers import _is_production_env
        cm = MagicMock()
        prod_env = _make_env("env-prod", "Production", is_production=True)
        cm.get_environments.return_value = [prod_env]

        assert _is_production_env("env-prod", cm) is True

    def test_via_env_name_contains_prod(self):
        from src.api.routes.assistant._resolvers import _is_production_env
        cm = MagicMock()
        env = _make_env("env-p", "PRODUCTION SERVER")
        cm.get_environments.return_value = [env]

        assert _is_production_env("env-p", cm) is True

    def test_cyrillic_prod(self):
        from src.api.routes.assistant._resolvers import _is_production_env
        assert _is_production_env("\u043f\u0440\u043e\u0434", MagicMock()) is True


class TestResolveProviderId:
    """_resolve_provider_id — LLM provider resolution."""

    def test_match_by_token(self):
        from src.api.routes.assistant._resolvers import _resolve_provider_id

        svc = MagicMock()
        p1 = MagicMock(id="p1", name="OpenAI", is_active=True)
        svc.get_all_providers.return_value = [p1]

        with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc):
            assert _resolve_provider_id("p1", MagicMock()) == "p1"
            assert _resolve_provider_id("OpenAI", MagicMock()) == "p1"

    def test_no_providers_returns_none(self):
        from src.api.routes.assistant._resolvers import _resolve_provider_id

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

        with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc):
            assert _resolve_provider_id(None, MagicMock()) is None

    def test_no_token_uses_active(self):
        from src.api.routes.assistant._resolvers import _resolve_provider_id

        svc = MagicMock()
        p1 = MagicMock(id="p1", name="Provider1", is_active=False)
        p2 = MagicMock(id="p2", name="Provider2", is_active=True)
        svc.get_all_providers.return_value = [p1, p2]

        with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc):
            assert _resolve_provider_id(None, MagicMock()) == "p2"

    def test_no_token_no_active_uses_first(self):
        from src.api.routes.assistant._resolvers import _resolve_provider_id

        svc = MagicMock()
        p1 = MagicMock(id="p1", name="Provider1", is_active=False)
        p2 = MagicMock(id="p2", name="Provider2", is_active=False)
        svc.get_all_providers.return_value = [p1, p2]

        with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc):
            assert _resolve_provider_id(None, MagicMock()) == "p1"

    def test_token_no_match_uses_bound_provider(self):
        from src.api.routes.assistant._resolvers import _resolve_provider_id

        db = MagicMock()
        cm = MagicMock()
        cm.get_config.return_value.settings.llm = MagicMock()

        svc = MagicMock()
        p1 = MagicMock(id="p1", name="OpenAI", is_active=False)
        svc.get_all_providers.return_value = [p1]

        with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc), \
             patch("src.api.routes.assistant._resolvers.resolve_bound_provider_id",
                   return_value="p1") as mock_bound:
            result = _resolve_provider_id("nonexistent_provider", db, config_manager=cm, task_key="validation")
            assert result == "p1"
            mock_bound.assert_called_once()

    def test_bound_provider_not_in_list_falls_through(self):
        from src.api.routes.assistant._resolvers import _resolve_provider_id

        db = MagicMock()
        cm = MagicMock()
        cm.get_config.return_value.settings.llm = MagicMock()

        svc = MagicMock()
        p1 = MagicMock(id="p1", name="OpenAI", is_active=True)
        svc.get_all_providers.return_value = [p1]

        with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc), \
             patch("src.api.routes.assistant._resolvers.resolve_bound_provider_id",
                   return_value="p_unknown"):
            result = _resolve_provider_id("nonexistent", db, config_manager=cm, task_key="x")
            assert result == "p1"  # falls through to active

    def test_config_error_does_not_crash(self):
        from src.api.routes.assistant._resolvers import _resolve_provider_id

        db = MagicMock()
        cm = MagicMock()
        cm.get_config.side_effect = RuntimeError("Config error")

        svc = MagicMock()
        p1 = MagicMock(id="p1", name="Provider", is_active=True)
        svc.get_all_providers.return_value = [p1]

        with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc):
            result = _resolve_provider_id(None, db, config_manager=cm, task_key="x")
            assert result == "p1"  # falls through to active


class TestGetDefaultEnvironmentId:
    """_get_default_environment_id — default env resolution."""

    def test_no_environments(self):
        from src.api.routes.assistant._resolvers import _get_default_environment_id
        cm = MagicMock()
        cm.get_environments.return_value = []
        assert _get_default_environment_id(cm) is None

    def test_uses_config_default(self):
        from src.api.routes.assistant._resolvers import _get_default_environment_id
        cm = MagicMock()
        envs = [_make_env("env-dev", "Development"), _make_env("env-prod", "Production")]
        cm.get_environments.return_value = envs
        cm.get_config.return_value.settings.default_environment_id = "env-dev"

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

    def test_config_default_not_in_list(self):
        from src.api.routes.assistant._resolvers import _get_default_environment_id
        cm = MagicMock()
        envs = [_make_env("env-dev", "Development")]
        cm.get_environments.return_value = envs
        cm.get_config.return_value.settings.default_environment_id = "missing"

        # Falls through to is_default flag or first
        assert _get_default_environment_id(cm) == "env-dev"

    def test_uses_is_default_flag(self):
        from src.api.routes.assistant._resolvers import _get_default_environment_id
        cm = MagicMock()
        envs = [
            _make_env("env-a", "First"),
            _make_env("env-b", "Default", is_default=True),
        ]
        cm.get_environments.return_value = envs
        cm.get_config.return_value.settings.default_environment_id = None

        assert _get_default_environment_id(cm) == "env-b"

    def test_falls_back_to_first(self):
        from src.api.routes.assistant._resolvers import _get_default_environment_id
        cm = MagicMock()
        envs = [_make_env("env-first", "First"), _make_env("env-second", "Second")]
        cm.get_environments.return_value = envs
        cm.get_config.return_value = None

        assert _get_default_environment_id(cm) == "env-first"

    def test_config_manager_no_get_config_attr(self):
        from src.api.routes.assistant._resolvers import _get_default_environment_id
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Dev")]
        # Remove get_config to simulate missing method
        if hasattr(cm, 'get_config'):
            del cm.get_config

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


class TestGetEnvironmentNameById:
    """_get_environment_name_by_id — human-readable name."""

    def test_found(self):
        from src.api.routes.assistant._resolvers import _get_environment_name_by_id
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Development")]
        assert _get_environment_name_by_id("env-dev", cm) == "Development"

    def test_not_found_returns_id(self):
        from src.api.routes.assistant._resolvers import _get_environment_name_by_id
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Development")]
        assert _get_environment_name_by_id("missing", cm) == "missing"

    def test_none_returns_unknown(self):
        from src.api.routes.assistant._resolvers import _get_environment_name_by_id
        assert _get_environment_name_by_id(None, MagicMock()) == "unknown"


class TestResolveDashboardIdByRef:
    """_resolve_dashboard_id_by_ref — dashboard resolution by slug/title."""

    @pytest.mark.asyncio
    async def test_none_ref(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
        assert await _resolve_dashboard_id_by_ref(None, "env-id", MagicMock()) is None

    @pytest.mark.asyncio
    async def test_no_env_id(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
        assert await _resolve_dashboard_id_by_ref("my-dash", None, MagicMock()) is None

    @pytest.mark.asyncio
    async def test_env_not_found(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("other-env", "Other")]
        assert await _resolve_dashboard_id_by_ref("my-dash", "env-dev", cm) is None

    @pytest.mark.asyncio
    async def test_matches_by_slug(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Dev")]

        with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_cls:
            client = MagicMock()
            mock_cls.return_value = client
            client.get_dashboards = AsyncMock(return_value=(None, [
                {"id": 42, "slug": "my-dash", "dashboard_title": "My Dashboard"},
                {"id": 99, "slug": "other", "dashboard_title": "Other"},
            ]))
            result = await _resolve_dashboard_id_by_ref("my-dash", "env-dev", cm)
            assert result == 42

    @pytest.mark.asyncio
    async def test_matches_by_title_exact(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Dev")]

        with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_cls:
            client = MagicMock()
            mock_cls.return_value = client
            client.get_dashboards = AsyncMock(return_value=(None, [
                {"id": 7, "slug": None, "dashboard_title": "Sales Dashboard"},
            ]))
            result = await _resolve_dashboard_id_by_ref("Sales Dashboard", "env-dev", cm)
            assert result == 7

    @pytest.mark.asyncio
    async def test_partial_title_match_unique(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Dev")]

        with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_cls:
            client = MagicMock()
            mock_cls.return_value = client
            client.get_dashboards = AsyncMock(return_value=(None, [
                {"id": 15, "slug": None, "dashboard_title": "Customer Analytics"},
                {"id": 20, "slug": None, "dashboard_title": "Finance Report"},
            ]))
            result = await _resolve_dashboard_id_by_ref("Analytics", "env-dev", cm)
            assert result == 15

    @pytest.mark.asyncio
    async def test_partial_title_match_ambiguous_returns_none(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Dev")]

        with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_cls:
            client = MagicMock()
            mock_cls.return_value = client
            client.get_dashboards = AsyncMock(return_value=(None, [
                {"id": 1, "slug": None, "dashboard_title": "Dashboard A"},
                {"id": 2, "slug": None, "dashboard_title": "Dashboard B"},
            ]))
            result = await _resolve_dashboard_id_by_ref("Dashboard", "env-dev", cm)
            assert result is None

    @pytest.mark.asyncio
    async def test_superset_error_returns_none(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Dev")]

        with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_cls:
            client = MagicMock()
            mock_cls.return_value = client
            client.get_dashboards = AsyncMock(side_effect=ConnectionError("API down"))
            result = await _resolve_dashboard_id_by_ref("my-dash", "env-dev", cm)
            assert result is None


class TestResolveDashboardIdEntity:
    """_resolve_dashboard_id_entity — entity resolution."""

    @pytest.mark.asyncio
    async def test_int_dashboard_id(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
        result = await _resolve_dashboard_id_entity({"dashboard_id": 42}, MagicMock())
        assert result == 42

    @pytest.mark.asyncio
    async def test_string_digit_dashboard_id(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
        result = await _resolve_dashboard_id_entity({"dashboard_id": "99"}, MagicMock())
        assert result == 99

    @pytest.mark.asyncio
    async def test_string_non_digit_becomes_ref(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Dev")]

        with patch("src.api.routes.assistant._resolvers._resolve_dashboard_id_by_ref",
                   new=AsyncMock(return_value=42)):
            result = await _resolve_dashboard_id_entity(
                {"dashboard_id": "my-slug"}, cm, env_hint="dev"
            )
            assert result == 42

    @pytest.mark.asyncio
    async def test_explicit_ref_alone(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Dev")]

        with patch("src.api.routes.assistant._resolvers._resolve_dashboard_id_by_ref",
                   new=AsyncMock(return_value=77)):
            result = await _resolve_dashboard_id_entity(
                {"dashboard_ref": "my-dash"}, cm
            )
            assert result == 77

    @pytest.mark.asyncio
    async def test_no_identifiers_returns_none(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
        result = await _resolve_dashboard_id_entity({}, MagicMock())
        assert result is None

    @pytest.mark.asyncio
    async def test_env_hint_overrides_entity_env(self):
        from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-prod", "Prod")]

        with patch("src.api.routes.assistant._resolvers._resolve_dashboard_id_by_ref",
                   new=AsyncMock(return_value=42)):
            result = await _resolve_dashboard_id_entity(
                {"dashboard_ref": "sales", "environment": "env-dev"},
                cm, env_hint="env-prod",
            )
            assert result == 42


class TestExtractResultDeepLinks:
    """_extract_result_deep_links — deep-link actions from task."""

    def test_migration_plugin(self):
        from src.api.routes.assistant._resolvers import _extract_result_deep_links
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("tgt-env", "Target")]

        task = MagicMock()
        task.plugin_id = "superset-migration"
        task.params = {"target_env_id": "tgt-env", "selected_ids": [42]}
        task.result = {"migrated_dashboards": [{"id": 42}]}

        actions = _extract_result_deep_links(task, cm)
        assert len(actions) >= 2  # open_route + open_diff
        assert any(a.type == "open_route" for a in actions)
        assert any(a.type == "open_diff" for a in actions)

    def test_migration_plugin_result_has_no_migrated_dashboards(self):
        from src.api.routes.assistant._resolvers import _extract_result_deep_links
        cm = MagicMock()

        task = MagicMock()
        task.plugin_id = "superset-migration"
        task.params = {"target_env_id": "env-prod", "selected_ids": [99]}
        task.result = {}  # no migrated_dashboards key

        actions = _extract_result_deep_links(task, cm)
        # Falls back to params.selected_ids
        assert len(actions) >= 1  # open_diff with id=99

    def test_backup_plugin(self):
        from src.api.routes.assistant._resolvers import _extract_result_deep_links
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Development")]

        task = MagicMock()
        task.plugin_id = "superset-backup"
        task.params = {"environment_id": "env-dev", "dashboard_ids": [7]}
        task.result = {"dashboards": [{"id": 7}], "environment": "env-dev"}

        actions = _extract_result_deep_links(task, cm)
        assert len(actions) >= 2

    def test_validation_plugin_without_env(self):
        from src.api.routes.assistant._resolvers import _extract_result_deep_links
        cm = MagicMock()

        task = MagicMock()
        task.plugin_id = "llm_dashboard_validation"
        task.params = {"dashboard_id": 42}  # no environment_id
        task.result = {}

        actions = _extract_result_deep_links(task, cm)
        assert len(actions) == 1  # open_diff only (no env -> no open_route)
        assert actions[0].type == "open_diff"

    def test_validation_plugin_with_env(self):
        from src.api.routes.assistant._resolvers import _extract_result_deep_links
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Development")]

        task = MagicMock()
        task.plugin_id = "llm_dashboard_validation"
        task.params = {"dashboard_id": 42, "environment_id": "env-dev"}
        task.result = {}

        actions = _extract_result_deep_links(task, cm)
        assert len(actions) == 2  # open_route + open_diff

    def test_unknown_plugin_no_actions(self):
        from src.api.routes.assistant._resolvers import _extract_result_deep_links
        cm = MagicMock()

        task = MagicMock()
        task.plugin_id = "some_unknown"
        task.params = {}
        task.result = {}

        actions = _extract_result_deep_links(task, cm)
        assert actions == []


class TestBuildTaskObservabilitySummary:
    """_build_task_observability_summary — task summary for different plugins."""

    def test_migration_success(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("tgt", "Target")]

        task = MagicMock()
        task.plugin_id = "superset-migration"
        task.status = "SUCCESS"
        task.params = {"target_env_id": "tgt"}
        task.result = {
            "migrated_dashboards": [{"id": 1}, {"id": 2}],
            "failed_dashboards": [],
            "selected_dashboards": 2,
            "mapping_count": 5,
        }

        summary = _build_task_observability_summary(task, cm)
        assert "Сводка миграции" in summary
        assert "перенесено 2" in summary
        assert "маппингов 5" in summary

    def test_migration_with_failures(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("tgt", "Target")]

        task = MagicMock()
        task.plugin_id = "superset-migration"
        task.status = "PARTIAL"
        task.params = {"target_env_id": "tgt"}
        task.result = {
            "migrated_dashboards": [{"id": 1}],
            "failed_dashboards": [{"id": 2, "title": "Broken", "error": "timeout"}],
            "selected_dashboards": 2,
            "mapping_count": 3,
        }

        summary = _build_task_observability_summary(task, cm)
        assert "Сводка миграции" in summary
        assert "с ошибками 1" in summary
        assert "Broken" in summary

    def test_migration_empty_lists(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("tgt", "Target")]

        task = MagicMock()
        task.plugin_id = "superset-migration"
        task.status = "SUCCESS"
        task.params = {"target_env_id": "tgt"}
        task.result = {
            "migrated_dashboards": [],
            "failed_dashboards": [],
            "mapping_count": 0,
        }

        summary = _build_task_observability_summary(task, cm)
        assert "Сводка миграции" in summary
        assert "перенесено 0" in summary

    def test_backup_success(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Development")]

        task = MagicMock()
        task.plugin_id = "superset-backup"
        task.status = "SUCCESS"
        task.params = {"environment_id": "env-dev"}
        task.result = {
            "total_dashboards": 10,
            "backed_up_dashboards": 10,
            "failed_dashboards": 0,
            "failures": [],
        }

        summary = _build_task_observability_summary(task, cm)
        assert "Сводка бэкапа" in summary
        assert "успешно 10" in summary
        assert "среда" in summary.lower()

    def test_backup_with_failures(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary
        cm = MagicMock()
        cm.get_environments.return_value = [_make_env("env-dev", "Development")]

        task = MagicMock()
        task.plugin_id = "superset-backup"
        task.status = "PARTIAL"
        task.params = {"environment_id": "env-dev"}
        task.result = {
            "total_dashboards": 5,
            "backed_up_dashboards": 3,
            "failed_dashboards": 2,
            "failures": [{"id": 4, "title": "Dashboard X", "error": "API error"}],
        }

        summary = _build_task_observability_summary(task, cm)
        assert "с ошибками 2" in summary
        assert "Dashboard X" in summary

    def test_validation_summary(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary
        cm = MagicMock()

        task = MagicMock()
        task.plugin_id = "llm_dashboard_validation"
        task.status = "SUCCESS"
        task.params = {}
        task.result = {
            "status": "passed",
            "summary": "All checks passed",
            "issues": [],
        }

        summary = _build_task_observability_summary(task, cm)
        assert "Сводка валидации" in summary
        assert "статус passed" in summary

    def test_validation_result_without_summary(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary
        cm = MagicMock()

        task = MagicMock()
        task.plugin_id = "llm_dashboard_validation"
        task.status = "FAILED"
        task.params = {}
        task.result = {}  # no status or summary

        summary = _build_task_observability_summary(task, cm)
        assert "Сводка валидации" in summary
        assert "Итог недоступен" in summary

    def test_unknown_plugin_terminal_status(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary
        cm = MagicMock()

        task = MagicMock()
        task.plugin_id = "custom-plugin"
        task.status = "SUCCESS"
        task.params = {}
        task.result = {}

        summary = _build_task_observability_summary(task, cm)
        assert "Задача завершена" in summary

    def test_unknown_plugin_non_terminal_status(self):
        from src.api.routes.assistant._resolvers import _build_task_observability_summary
        cm = MagicMock()

        task = MagicMock()
        task.plugin_id = "custom-plugin"
        task.status = "RUNNING"
        task.params = {}
        task.result = {}

        summary = _build_task_observability_summary(task, cm)
        assert summary == ""


# #endregion Test.Assistant.Resolvers
