# #region Test.DatasetsPreviewFiltersMixin [C:3] [TYPE Module] [SEMANTICS test, dataset, preview, filter, sql, extract]
# @BRIEF Tests for core/superset_client/_datasets_preview_filters.py — filter normalization + SQL extraction.
# @RELATION BINDS_TO -> [SupersetDatasetsPreviewFiltersMixin]

from pathlib import Path
import sys

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

from unittest.mock import MagicMock, patch

import pytest


class TestNormalizeEffectiveFilters:
    """_normalize_effective_filters_for_query_context: filter conversion."""

    @pytest.fixture
    def mixin(self):
        from src.core.superset_client._datasets_preview_filters import (
            SupersetDatasetsPreviewFiltersMixin,
        )

        return SupersetDatasetsPreviewFiltersMixin()

    def test_empty_filters(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context([])
        assert result["filters"] == []
        assert result["diagnostics"] == []

    def test_skip_non_dict_items(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context(
            ["string", 42, None]
        )
        assert result["filters"] == []
        assert len(result["diagnostics"]) == 0  # skipped before diagnostics

    def test_heuristic_reconstruction_with_column_and_value(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context([
            {"variable_name": "status", "effective_value": "active"}
        ])
        assert len(result["filters"]) == 1
        assert result["filters"][0]["col"] == "status"
        assert result["filters"][0]["op"] == "=="
        assert result["filters"][0]["val"] == "active"

    def test_heuristic_reconstruction_with_list_value(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context([
            {"variable_name": "status", "effective_value": ["active", "pending"]}
        ])
        assert result["filters"][0]["op"] == "IN"

    def test_heuristic_reconstruction_missing_column_skipped(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context([
            {"effective_value": "val"}
        ])
        assert result["filters"] == []

    def test_preserved_clauses_without_val(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context([
            {
                "display_name": "Category",
                "effective_value": "electronics",
                "normalized_filter_payload": {
                    "filter_clauses": [{"col": "category", "op": "=="}],
                    "value_origin": "preset",
                },
            }
        ])
        assert len(result["filters"]) == 1
        # val should be filled from effective_value
        assert result["filters"][0]["val"] == "electronics"

    def test_preserved_clauses_with_existing_val(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context([
            {
                "display_name": "Category",
                "effective_value": "fallback",
                "normalized_filter_payload": {
                    "filter_clauses": [{"col": "category", "op": "==", "val": "original"}],
                    "value_origin": "preset",
                },
            }
        ])
        assert len(result["filters"]) == 1
        # val should remain "original" since it already exists
        assert result["filters"][0]["val"] == "original"

    def test_extra_form_data_merged(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context([
            {
                "display_name": "Filter1",
                "effective_value": "val1",
                "normalized_filter_payload": {
                    "extra_form_data": {"time_range": "Last month", "filters": ["ignored"]},
                    "filter_clauses": [],
                },
            }
        ])
        # extra_form_data.filters is skipped during merge
        assert result["extra_form_data"]["time_range"] == "Last month"
        assert "filters" not in result["extra_form_data"]

    def test_extra_form_data_without_preserved_clauses_uses_empty(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context([
            {
                "display_name": "Filter1",
                "effective_value": None,
                "normalized_filter_payload": {
                    "extra_form_data": {"time_range": "All time"},
                },
            }
        ])
        # Has extra_form_data but no clauses -> empty clauses
        assert result["filters"] == []

    def test_missing_display_name_fallback(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context([
            {
                "filter_name": "my_filter",
                "variable_name": "my_var",
                "effective_value": "val",
            }
        ])
        diagnostics = result["diagnostics"]
        assert diagnostics[0]["filter_name"] == "my_filter"

    def test_unnamed_filter_fallback(self, mixin):
        result = mixin._normalize_effective_filters_for_query_context([
            {"effective_value": "x"}
        ])
        assert result["filters"] == []


class TestExtractCompiledSql:
    """_extract_compiled_sql_from_preview_response: normalized SQL from various response shapes."""

    @pytest.fixture
    def mixin(self):
        from src.core.superset_client._datasets_preview_filters import (
            SupersetDatasetsPreviewFiltersMixin,
        )

        return SupersetDatasetsPreviewFiltersMixin()

    def test_result_list_with_query(self, mixin):
        response = {
            "result": [
                {"query": "SELECT 1", "status": "success", "applied_filters": []},
                {"query": "SELECT 2", "status": "success"},
            ]
        }
        result = mixin._extract_compiled_sql_from_preview_response(response)
        assert result["compiled_sql"] == "SELECT 1"

    def test_result_list_with_sql_field(self, mixin):
        response = {
            "result": [
                {"sql": "SELECT * FROM users", "status": "success"},
            ]
        }
        result = mixin._extract_compiled_sql_from_preview_response(response)
        assert result["compiled_sql"] == "SELECT * FROM users"

    def test_result_list_with_compiled_sql(self, mixin):
        response = {
            "result": [
                {"compiled_sql": "SELECT count(*) FROM t", "status": "success"},
            ]
        }
        result = mixin._extract_compiled_sql_from_preview_response(response)
        assert result["compiled_sql"] == "SELECT count(*) FROM t"

    def test_result_list_empty_returns_top_level(self, mixin):
        response = {
            "result": [
                {"status": "success", "applied_filters": []},
            ],
            "query": "SELECT top FROM dual",
        }
        result = mixin._extract_compiled_sql_from_preview_response(response)
        assert result["compiled_sql"] == "SELECT top FROM dual"

    def test_result_dict_with_query(self, mixin):
        response = {
            "result": {"query": "SELECT nested FROM result_dict"}
        }
        result = mixin._extract_compiled_sql_from_preview_response(response)
        assert result["compiled_sql"] == "SELECT nested FROM result_dict"

    def test_result_dict_fallback_to_top_level(self, mixin):
        response = {
            "result": {"status": "ok"},
            "sql": "SELECT fallback FROM top",
        }
        result = mixin._extract_compiled_sql_from_preview_response(response)
        assert result["compiled_sql"] == "SELECT fallback FROM top"

    def test_result_dict_empty_result_obj_falls_through(self, mixin):
        response = {
            "result": {},
            "compiled_sql": "SELECT fallback FROM compiled",
        }
        result = mixin._extract_compiled_sql_from_preview_response(response)
        assert result["compiled_sql"] == "SELECT fallback FROM compiled"

    def test_no_sql_raises_error(self, mixin):
        from src.core.utils.network import SupersetAPIError

        response = {"result": []}
        with pytest.raises(SupersetAPIError, match="Superset preview response did not expose compiled SQL"):
            mixin._extract_compiled_sql_from_preview_response(response)

    def test_non_dict_response_raises_error(self, mixin):
        from src.core.utils.network import SupersetAPIError

        with pytest.raises(SupersetAPIError, match="Superset preview response was not a JSON object"):
            mixin._extract_compiled_sql_from_preview_response("not_a_dict")

    def test_whitespace_only_sql_is_empty(self, mixin):
        from src.core.utils.network import SupersetAPIError

        response = {"result": [{"query": "   "}]}
        with pytest.raises(SupersetAPIError):
            mixin._extract_compiled_sql_from_preview_response(response)

    def test_result_list_skips_non_dict_items(self, mixin):
        response = {
            "result": ["string_item", 42, {"query": "SELECT 1"}]
        }
        result = mixin._extract_compiled_sql_from_preview_response(response)
        assert result["compiled_sql"] == "SELECT 1"

    def test_response_diagnostics_includes_all_attempts(self, mixin):
        response = {"query": "SELECT 1"}
        result = mixin._extract_compiled_sql_from_preview_response(response)
        assert len(result["response_diagnostics"]) >= 1
        assert result["response_diagnostics"][0]["source"] == "query"
# #endregion Test.DatasetsPreviewFiltersMixin
