# #region Test.DebugPlugin [C:3] [TYPE Module] [SEMANTICS test, debug, plugin, coverage]
# @BRIEF Unit tests for DebugPlugin — properties, get_schema, execute, and edge cases.
# @RELATION BINDS_TO -> [DebugPlugin]
# @TEST_EDGE: unknown_action -> Raises ValueError
# @TEST_EDGE: missing_source_target_env -> Raises ValueError
# @TEST_EDGE: missing_env_dataset_id -> Raises ValueError
# @TEST_EDGE: env_not_found -> Raises ValueError

import pytest
from unittest.mock import AsyncMock, MagicMock, patch, PropertyMock

from src.plugins.debug import DebugPlugin


# ── Helpers ──


def _make_env(id_val="env-1", name="Test Env"):
    env = MagicMock()
    env.id = id_val
    env.name = name
    return env


def _make_task_context():
    ctx = MagicMock()
    ctx.logger = MagicMock()
    ctx.logger.with_source.return_value = MagicMock()
    return ctx


class TestDebugPluginProperties:
    """Verify static property values."""

    def test_id(self):
        plugin = DebugPlugin()
        assert plugin.id == "system-debug"

    def test_name(self):
        plugin = DebugPlugin()
        assert plugin.name == "System Debug"

    def test_description(self):
        plugin = DebugPlugin()
        assert plugin.description == "Run system diagnostics and debug Superset API responses."

    def test_version(self):
        plugin = DebugPlugin()
        assert plugin.version == "1.0.0"

    def test_ui_route(self):
        plugin = DebugPlugin()
        assert plugin.ui_route == "/tools/debug"


class TestDebugPluginGetSchema:
    """Verify get_schema returns correct structure."""

    def test_get_schema(self):
        plugin = DebugPlugin()
        schema = plugin.get_schema()

        assert schema["type"] == "object"
        assert "action" in schema["properties"]
        assert schema["properties"]["action"]["enum"] == ["test-db-api", "get-dataset-structure"]
        assert "action" in schema["required"]


class TestDebugPluginExecute:
    """Verify DebugPlugin.execute with various actions."""

    # ── Unknown action → ValueError ──

    @pytest.mark.asyncio
    async def test_execute_unknown_action(self):
        """Unknown action raises ValueError."""
        plugin = DebugPlugin()
        with pytest.raises(ValueError, match="Unknown action: bogus"):
            await plugin.execute({"action": "bogus"})

    @pytest.mark.asyncio
    async def test_execute_unknown_action_with_ctx(self):
        """Unknown action raises ValueError even with context."""
        plugin = DebugPlugin()
        ctx = _make_task_context()
        with pytest.raises(ValueError, match="Unknown action: bogus"):
            await plugin.execute({"action": "bogus"}, context=ctx)

    # ── test-db-api: missing source/target ──

    @pytest.mark.asyncio
    async def test_execute_test_db_api_missing_params(self):
        """test-db-api without source_env/target_env raises ValueError."""
        plugin = DebugPlugin()
        with pytest.raises(ValueError, match="source_env and target_env are required"):
            await plugin.execute({"action": "test-db-api", "source_env": "src"})

    @pytest.mark.asyncio
    async def test_execute_test_db_api_no_params(self):
        """test-db-api without any params raises ValueError."""
        plugin = DebugPlugin()
        with pytest.raises(ValueError, match="source_env and target_env are required"):
            await plugin.execute({"action": "test-db-api"})

    # ── test-db-api: env not found ──

    @pytest.mark.asyncio
    async def test_execute_test_db_api_env_not_found(self):
        """test-db-api with non-existent env raises ValueError."""
        plugin = DebugPlugin()
        mock_cm = MagicMock()
        mock_cm.get_environment.return_value = None

        with patch('src.dependencies.get_config_manager', return_value=mock_cm):
            with pytest.raises(ValueError, match="not found"):
                await plugin.execute({
                    "action": "test-db-api",
                    "source_env": "NonExistent",
                    "target_env": "AlsoMissing",
                })

    # ── test-db-api: happy path ──

    @pytest.mark.asyncio
    async def test_execute_test_db_api_success(self):
        """test-db-api completes successfully with both envs."""
        plugin = DebugPlugin()
        src_env = _make_env("src-id", "Source")
        tgt_env = _make_env("tgt-id", "Target")

        mock_cm = MagicMock()
        mock_cm.get_environment.side_effect = [src_env, tgt_env]

        mock_client = MagicMock()
        mock_client.authenticate = AsyncMock()
        mock_client.get_databases = AsyncMock(return_value=(3, [{"id": 1, "name": "db1"}]))

        with patch('src.dependencies.get_config_manager', return_value=mock_cm), \
             patch('src.plugins.debug.get_superset_client') as mock_get_client:
            mock_get_client.side_effect = [mock_client, mock_client]

            result = await plugin.execute({
                "action": "test-db-api",
                "source_env": "Source",
                "target_env": "Target",
            })

        assert "Source" in result
        assert "Target" in result
        assert result["Source"]["count"] == 3
        assert result["Target"]["count"] == 3

    # ── get-dataset-structure: missing params ──

    @pytest.mark.asyncio
    async def test_execute_get_dataset_structure_missing_env(self):
        """get-dataset-structure without env raises ValueError."""
        plugin = DebugPlugin()
        with pytest.raises(ValueError, match="env and dataset_id are required"):
            await plugin.execute({"action": "get-dataset-structure", "dataset_id": 42})

    @pytest.mark.asyncio
    async def test_execute_get_dataset_structure_missing_dataset_id(self):
        """get-dataset-structure without dataset_id raises ValueError."""
        plugin = DebugPlugin()
        with pytest.raises(ValueError, match="env and dataset_id are required"):
            await plugin.execute({"action": "get-dataset-structure", "env": "Dev"})

    # ── get-dataset-structure: env not found ──

    @pytest.mark.asyncio
    async def test_execute_get_dataset_structure_env_not_found(self):
        """get-dataset-structure with non-existent env raises ValueError."""
        plugin = DebugPlugin()
        mock_cm = MagicMock()
        mock_cm.get_environment.return_value = None

        with patch('src.dependencies.get_config_manager', return_value=mock_cm):
            with pytest.raises(ValueError, match="not found"):
                await plugin.execute({
                    "action": "get-dataset-structure",
                    "env": "Missing",
                    "dataset_id": 1,
                })

    # ── get-dataset-structure: happy path ──

    @pytest.mark.asyncio
    async def test_execute_get_dataset_structure_success(self):
        """get-dataset-structure returns dataset result."""
        plugin = DebugPlugin()
        env = _make_env("dev-id", "Dev")
        mock_cm = MagicMock()
        mock_cm.get_environment.return_value = env

        mock_client = MagicMock()
        mock_client.authenticate = AsyncMock()
        mock_client.get_dataset = AsyncMock(return_value={
            "result": {"id": 42, "table_name": "my_table"},
        })

        with patch('src.dependencies.get_config_manager', return_value=mock_cm), \
             patch('src.plugins.debug.get_superset_client') as mock_get_client:
            mock_get_client.return_value = mock_client

            result = await plugin.execute({
                "action": "get-dataset-structure",
                "env": "Dev",
                "dataset_id": 42,
            })

        assert result == {"id": 42, "table_name": "my_table"}

    # ── get-dataset-structure: empty result ──

    @pytest.mark.asyncio
    async def test_execute_get_dataset_structure_empty_result(self):
        """get-dataset-structure returns empty dict when result field is missing."""
        plugin = DebugPlugin()
        env = _make_env("dev-id", "Dev")
        mock_cm = MagicMock()
        mock_cm.get_environment.return_value = env

        mock_client = MagicMock()
        mock_client.authenticate = AsyncMock()
        mock_client.get_dataset = AsyncMock(return_value={"some_other_key": "value"})

        with patch('src.dependencies.get_config_manager', return_value=mock_cm), \
             patch('src.plugins.debug.get_superset_client') as mock_get_client:
            mock_get_client.return_value = mock_client

            result = await plugin.execute({
                "action": "get-dataset-structure",
                "env": "Dev",
                "dataset_id": 99,
            })

        assert result == {}
