# region test_llm_plugin_persistence [TYPE Module]
# @RELATION  BINDS_TO -> [DashboardValidationPlugin]
# @PURPOSE: Regression test for ValidationRecord persistence fields populated from task context.

import pytest
import types

from src.plugins.llm_analysis import plugin as plugin_module


# region _DummyLogger [TYPE Class]
# @RELATION  BINDS_TO -> [EXT:frontend:test_llm_plugin_persistence]
# @PURPOSE: Minimal logger shim for TaskContext-like objects used in tests.
# @INVARIANT  Logging methods are no-ops and must not mutate test state.
class _DummyLogger:
    def with_source(self, _source: str):
        return self

    def info(self, *_args, **_kwargs):
        return None

    def debug(self, *_args, **_kwargs):
        return None

    def warning(self, *_args, **_kwargs):
        return None

    def error(self, *_args, **_kwargs):
        return None


# endregion _DummyLogger


# region _FakeDBSession [TYPE Class]
# @RELATION  BINDS_TO -> [EXT:frontend:test_llm_plugin_persistence]
# @PURPOSE: Captures persisted records for assertion and mimics SQLAlchemy session methods used by plugin.
# @INVARIANT  add/commit/close provide only persistence signals asserted by this test.
class _FakeDBSession:
    def __init__(self):
        self.added = None
        self.committed = False
        self.closed = False

    def add(self, obj):
        self.added = obj

    def commit(self):
        self.committed = True

    def close(self):
        self.closed = True


# endregion _FakeDBSession


# region test_dashboard_validation_plugin_persists_task_and_environment_ids [TYPE Function]
# @RELATION  BINDS_TO -> [EXT:frontend:test_llm_plugin_persistence]
# @RELATION  BINDS_TO -> [DashboardValidationPlugin]
# @PURPOSE: Ensure db ValidationRecord includes context.task_id and params.environment_id.
# @INVARIANT  Assertions remain restricted to persisted task/environment identity fields and session lifecycle signals.
@pytest.mark.asyncio
async def test_dashboard_validation_plugin_persists_task_and_environment_ids(
    tmp_path, monkeypatch
):
    fake_db = _FakeDBSession()

    env = types.SimpleNamespace(id="env-42")
    provider = types.SimpleNamespace(
        id="provider-1",
        name="Main LLM",
        provider_type="openai",
        base_url="https://example.invalid/v1",
        default_model="gpt-4o",
        is_active=True,
        is_multimodal=True,
    )

    # region _FakeProviderService [TYPE Class]
    # @RELATION BINDS_TO -> [EXT:frontend:test_dashboard_validation_plugin_persists_task_and_environment_ids]
    # @PURPOSE: LLM provider service stub returning deterministic provider and decrypted API key for plugin tests.
    # @INVARIANT  Returns same provider and key regardless of provider_id argument; no lookup logic.
    class _FakeProviderService:
        def __init__(self, _db):
            return None

        def get_provider(self, _provider_id):
            return provider

        def get_decrypted_api_key(self, _provider_id):
            return "a" * 32

    # endregion _FakeProviderService

    # region _FakeScreenshotService [TYPE Class]
    # @RELATION BINDS_TO -> [EXT:frontend:test_dashboard_validation_plugin_persists_task_and_environment_ids]
    # @PURPOSE: Screenshot service stub that accepts capture_dashboard calls without side effects.
    # @INVARIANT  capture_dashboard is intentionally permissive for this persistence-focused test and does not validate argument values.
    class _FakeScreenshotService:
        def __init__(self, _env):
            return None

        async def capture_dashboard(self, _dashboard_id, _screenshot_path):
            return [], []

        @staticmethod
        def _cleanup_temp_files(_paths):
            pass

    # endregion _FakeScreenshotService

    # region _FakeLLMClient [TYPE Class]
    # @RELATION BINDS_TO -> [EXT:frontend:test_dashboard_validation_plugin_persists_task_and_environment_ids]
    # @PURPOSE: Deterministic LLM client double returning canonical analysis payload for persistence-path assertions.
    # @INVARIANT  analyze_dashboard is side-effect free and returns schema-compatible PASS result.
    class _FakeLLMClient:
        """Fake LLM client for persistence tests.

        Always returns PASS status. FAIL and UNKNOWN persistence branches are NOT
        covered by this fake — see coverage-planner findings.
        """

        def __init__(self, **_kwargs):
            return None

        async def analyze_dashboard(self, *_args, **_kwargs):
            return {
                "status": "PASS",
                "summary": "Dashboard healthy",
                "issues": [],
            }

        async def analyze_dashboard_multimodal(self, *_args, **_kwargs):
            return {
                "status": "PASS",
                "summary": "Dashboard healthy",
                "issues": [],
            }

        async def analyze_dashboard_text_batch(self, *_args, **_kwargs):
            return {
                "dashboards": [{
                    "status": "PASS",
                    "summary": "Dashboard healthy",
                    "issues": [],
                }],
            }

    # endregion _FakeLLMClient

    # region _FakeNotificationService [TYPE Class]
    # @RELATION BINDS_TO -> [EXT:frontend:test_dashboard_validation_plugin_persists_task_and_environment_ids]
    # @PURPOSE: Notification service stub that accepts plugin dispatch_report payload without introducing side effects.
    # @INVARIANT  dispatch_report accepts arbitrary keyword payloads because this test verifies persistence fields, not notification payload schema.
    class _FakeNotificationService:
        def __init__(self, *_args, **_kwargs):
            return None

        async def dispatch_report(self, **_kwargs):
            return None

    # endregion _FakeNotificationService

    # region _FakeConfigManager [TYPE Class]
    # @RELATION BINDS_TO -> [EXT:frontend:test_dashboard_validation_plugin_persists_task_and_environment_ids]
    # @PURPOSE: Config manager stub providing storage root path and minimal settings for plugin execution path.
    # @INVARIANT  Only storage.root_path and llm fields are safe to access; all other settings fields are absent.
    class _FakeConfigManager:
        def get_environment(self, _env_id):
            return env

        def get_config(self):
            return types.SimpleNamespace(
                settings=types.SimpleNamespace(
                    storage=types.SimpleNamespace(root_path=str(tmp_path)),
                    llm={},
                )
            )

    # endregion _FakeConfigManager

    # region _FakeSupersetClient [TYPE Class]
    # @RELATION BINDS_TO -> [EXT:frontend:test_dashboard_validation_plugin_persists_task_and_environment_ids]
    # @PURPOSE: Superset client stub exposing network.request as a lambda that returns empty result list.
    # @INVARIANT  network.request intentionally accepts arbitrary keyword payloads because response shape, not request signature, is the persistence-path dependency.
    class _FakeSupersetClient:
        def __init__(self, _env):
            self.network = types.SimpleNamespace(
                request=lambda **_kwargs: {"result": []}
            )

    # endregion _FakeSupersetClient

    monkeypatch.setattr(plugin_module, "SessionLocal", lambda: fake_db)
    monkeypatch.setattr(plugin_module, "LLMProviderService", _FakeProviderService)
    monkeypatch.setattr(plugin_module, "ScreenshotService", _FakeScreenshotService)
    monkeypatch.setattr(plugin_module, "LLMClient", _FakeLLMClient)
    monkeypatch.setattr(plugin_module, "NotificationService", _FakeNotificationService)
    monkeypatch.setattr(plugin_module, "SupersetClient", _FakeSupersetClient)
    monkeypatch.setattr(
        "src.dependencies.get_config_manager", lambda: _FakeConfigManager()
    )

    context = types.SimpleNamespace(
        task_id="task-999",
        logger=_DummyLogger(),
        background_tasks=None,
    )

    plugin = plugin_module.DashboardValidationPlugin()
    result = await plugin.execute(
        {
            "dashboard_id": "11",
            "environment_id": "env-42",
            "provider_id": "provider-1",
        },
        context=context,
    )

    # Plugin now returns a batch result with all dashboards
    assert result["total"] == 1
    assert result["dashboards"][0]["environment_id"] == "env-42"
    assert fake_db.committed is True
    assert fake_db.closed is True
    assert fake_db.added is not None
    assert fake_db.added.task_id == "task-999"
    assert fake_db.added.environment_id == "env-42"


# endregion test_dashboard_validation_plugin_persists_task_and_environment_ids


# endregion test_llm_plugin_persistence
