# #region Test.DatasetMapper [C:3] [TYPE Module] [SEMANTICS test, dataset, mapper, sqllab, excel]
# @BRIEF Tests for core/utils/dataset_mapper.py — DatasetMapper.
# @RELATION BINDS_TO -> [DatasetMapperModule]

from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))

from unittest.mock import AsyncMock, MagicMock, call, patch

import pandas as pd
import pytest


class TestDatasetMapper:
    """Test DatasetMapper — SQL Lab and Excel mapping + run."""

    @pytest.fixture
    def mapper(self):
        from src.core.utils.dataset_mapper import DatasetMapper

        return DatasetMapper()

    # ── get_sqllab_mappings ──

    @pytest.mark.asyncio
    async def test_get_sqllab_mappings_with_default_query(self, mapper):
        client = MagicMock()
        client.get_dataset = AsyncMock(
            return_value={
                "result": {
                    "table_name": "users",
                    "schema": "public",
                    "database": {"database_name": "Postgres"},
                }
            }
        )
        sqllab_executor = AsyncMock()
        sqllab_executor.execute_and_poll.return_value = {
            "result": [
                {"column_name": "id", "verbose_name": "Identifier"},
                {"column_name": "name", "verbose_name": "Full Name"},
            ]
        }

        result = await mapper.get_sqllab_mappings(client, 1, sqllab_executor, 42)
        assert result == {"id": "Identifier", "name": "Full Name"}
        assert "information_schema" in sqllab_executor.execute_and_poll.call_args[0][0]
        sqllab_executor.execute_and_poll.assert_awaited_once()

    @pytest.mark.asyncio
    async def test_get_sqllab_mappings_with_custom_query(self, mapper):
        client = MagicMock()
        client.get_dataset = AsyncMock(
            return_value={
                "result": {
                    "table_name": "users",
                    "schema": "public",
                    "database": {"database_name": "Postgres"},
                }
            }
        )
        sqllab_executor = AsyncMock()
        sqllab_executor.execute_and_poll.return_value = {
            "data": [
                {"column_name": "email", "verbose_name": "Email Address"},
            ]
        }

        result = await mapper.get_sqllab_mappings(
            client, 1, sqllab_executor, 42, sql_query="SELECT column_name, verbose_name FROM my_table"
        )
        assert result == {"email": "Email Address"}
        assert "my_table" in sqllab_executor.execute_and_poll.call_args[0][0]

    @pytest.mark.asyncio
    async def test_get_sqllab_mappings_skips_null_verbose_name(self, mapper):
        client = MagicMock()
        client.get_dataset = AsyncMock(
            return_value={"result": {"table_name": "t", "schema": "public"}}
        )
        sqllab_executor = AsyncMock()
        sqllab_executor.execute_and_poll.return_value = {
            "results": [
                {"column_name": "id", "verbose_name": None},
                {"column_name": "name", "verbose_name": "Name"},
            ]
        }

        result = await mapper.get_sqllab_mappings(client, 1, sqllab_executor, 42)
        assert result == {"name": "Name"}

    @pytest.mark.asyncio
    async def test_get_sqllab_mappings_uses_default_schema(self, mapper):
        client = MagicMock()
        client.get_dataset = AsyncMock(
            return_value={"result": {"table_name": "t"}}
        )
        sqllab_executor = AsyncMock()
        sqllab_executor.execute_and_poll.return_value = {"result": []}

        await mapper.get_sqllab_mappings(client, 1, sqllab_executor, 42)
        assert "public" in sqllab_executor.execute_and_poll.call_args[0][0]

    # ── load_excel_mappings ──

    def test_load_excel_mappings_success(self, mapper):
        """Use a real DataFrame to avoid MagicMock complexity with __getitem__."""
        data = pd.DataFrame({"column_name": ["a", "b"], "verbose_name": ["Alpha", "Beta"]})
        with patch("pandas.read_excel", return_value=data):
            result = mapper.load_excel_mappings("/tmp/test.xlsx")
            assert result == {"a": "Alpha", "b": "Beta"}

    def test_load_excel_mappings_file_not_found(self, mapper):
        with patch("pandas.read_excel", side_effect=FileNotFoundError("No file")):
            with pytest.raises(FileNotFoundError):
                mapper.load_excel_mappings("/nonexistent.xlsx")

    # ── run_mapping ──

    @pytest.mark.asyncio
    async def test_run_mapping_sqllab_with_changes(self, mapper):
        # Mock get_sqllab_mappings to avoid its missing-await issue
        mapper.get_sqllab_mappings = AsyncMock(
            return_value={"id": "Identifier", "name": "New Name"}
        )
        client = AsyncMock()
        client.get_dataset = AsyncMock(
            return_value={
                "result": {
                    "table_name": "users",
                    "columns": [
                        {"column_name": "id", "verbose_name": None},
                        {"column_name": "name", "verbose_name": "Old Name"},
                    ],
                    "metrics": [],
                    "owners": [],
                    "database": {"id": 1},
                }
            }
        )
        client.update_dataset = AsyncMock(return_value={"result": "ok"})

        await mapper.run_mapping(
            superset_client=client,
            dataset_id=1,
            source="sqllab",
            sqllab_executor=MagicMock(),
            database_id=42,
        )
        client.update_dataset.assert_awaited_once()
        call_args = client.update_dataset.call_args
        assert call_args[0][0] == 1  # dataset_id

    @pytest.mark.asyncio
    async def test_run_mapping_sqllab_no_changes(self, mapper):
        mapper.get_sqllab_mappings = AsyncMock(
            return_value={"name": "Same Name"}
        )
        client = AsyncMock()
        client.get_dataset = AsyncMock(
            return_value={
                "result": {
                    "table_name": "users",
                    "columns": [
                        {"column_name": "name", "verbose_name": "Same Name"},
                    ],
                    "metrics": [],
                    "owners": [],
                    "database": {"id": 1},
                }
            }
        )
        client.update_dataset = AsyncMock()

        await mapper.run_mapping(
            superset_client=client,
            dataset_id=1,
            source="sqllab",
            sqllab_executor=MagicMock(),
            database_id=42,
        )
        client.update_dataset.assert_not_awaited()

    @pytest.mark.asyncio
    async def test_run_mapping_excel_source(self, mapper):
        mapper.load_excel_mappings = MagicMock(return_value={"a": "Alpha"})
        client = AsyncMock()
        client.get_dataset = AsyncMock(
            return_value={
                "result": {
                    "table_name": "t",
                    "columns": [{"column_name": "a", "verbose_name": None}],
                    "metrics": [],
                    "owners": [],
                    "database": {"id": 1},
                }
            }
        )
        client.update_dataset = AsyncMock(return_value={"result": "ok"})

        await mapper.run_mapping(
            superset_client=client, dataset_id=1, source="excel", excel_path="/tmp/map.xlsx"
        )
        client.update_dataset.assert_awaited_once()

    @pytest.mark.asyncio
    async def test_run_mapping_invalid_source(self, mapper):
        client = MagicMock()
        with patch("src.core.utils.dataset_mapper.app_logger") as mock_log:
            await mapper.run_mapping(
                superset_client=client, dataset_id=1, source="invalid"
            )
            mock_log.explore.assert_called_once()

    @pytest.mark.asyncio
    async def test_run_mapping_missing_sqllab_args(self, mapper):
        client = MagicMock()
        with patch("src.core.utils.dataset_mapper.app_logger") as mock_log:
            await mapper.run_mapping(
                superset_client=client, dataset_id=1, source="sqllab",
                sqllab_executor=None, database_id=None,
            )
            mock_log.explore.assert_called_once()

    @pytest.mark.asyncio
    async def test_run_mapping_missing_excel_path(self, mapper):
        client = MagicMock()
        with patch("src.core.utils.dataset_mapper.app_logger") as mock_log:
            await mapper.run_mapping(
                superset_client=client, dataset_id=1, source="excel", excel_path=None,
            )
            mock_log.explore.assert_called_once()

    @pytest.mark.asyncio
    async def test_run_mapping_client_error_handled(self, mapper):
        mapper.get_sqllab_mappings = AsyncMock(side_effect=Exception("API failure"))
        client = MagicMock()

        with patch("src.core.utils.dataset_mapper.app_logger") as mock_log:
            await mapper.run_mapping(
                superset_client=client,
                dataset_id=1,
                source="sqllab",
                sqllab_executor=MagicMock(),
                database_id=42,
            )
            mock_log.explore.assert_called_once()

    @pytest.mark.asyncio
    async def test_run_mapping_handles_non_dict_columns(self, mapper):
        mapper.get_sqllab_mappings = AsyncMock(return_value={"a": "Alpha"})
        client = AsyncMock()
        client.get_dataset = AsyncMock(
            return_value={
                "result": {
                    "table_name": "t",
                    "columns": [{"column_name": "a", "verbose_name": None}],
                    "metrics": [],
                    "owners": [],
                    "database": {"id": 1},
                }
            }
        )
        client.update_dataset = AsyncMock(return_value={"result": "ok"})

        await mapper.run_mapping(
            superset_client=client, dataset_id=1, source="sqllab",
            sqllab_executor=MagicMock(), database_id=42,
        )
        client.update_dataset.assert_awaited_once()
# #endregion Test.DatasetMapper
