# #region Test.SupersetContextExtractorBase [C:3] [TYPE Module] [SEMANTICS test, superset, context, extract, parse]
# @BRIEF Tests for core/utils/superset_context_extractor/_base.py — SupersetContextExtractorBase.
# @RELATION BINDS_TO -> [SupersetContextExtractorBase]

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


@pytest.fixture(autouse=True)
def patch_env():
    with patch("src.core.utils.superset_context_extractor._base.Environment") as mock_env, \
         patch("src.core.utils.superset_context_extractor._base.SupersetClient") as mock_sc:
        env_instance = MagicMock()
        env_instance.id = "test-env"
        env_instance.url = "https://superset.example.com"
        mock_env.return_value = env_instance

        mock_sc_instance = MagicMock()
        mock_sc.return_value = mock_sc_instance

        yield {
            "env": env_instance,
            "sc": mock_sc_instance,
        }


class TestSupersetParsedContext:
    """SupersetParsedContext dataclass."""

    def test_default_values(self):
        from src.core.utils.superset_context_extractor._base import (
            SupersetParsedContext,
        )

        ctx = SupersetParsedContext(
            source_url="https://example.com",
            dataset_ref="ds_1",
        )
        assert ctx.source_url == "https://example.com"
        assert ctx.dataset_ref == "ds_1"
        assert ctx.dataset_id is None
        assert ctx.resource_type == "unknown"
        assert ctx.imported_filters == []
        assert ctx.partial_recovery is False


class TestSupersetContextExtractorBaseInit:
    """__init__: binds environment and client."""

    def test_init_without_client(self, patch_env):
        from src.core.utils.superset_context_extractor._base import (
            SupersetContextExtractorBase,
        )

        extractor = SupersetContextExtractorBase(environment=patch_env["env"])
        assert extractor.environment is patch_env["env"]

    def test_init_with_client(self, patch_env):
        from src.core.utils.superset_context_extractor._base import (
            SupersetContextExtractorBase,
        )

        client = MagicMock()
        extractor = SupersetContextExtractorBase(
            environment=patch_env["env"], client=client
        )
        assert extractor.client is client


class TestBuildRecoverySummary:
    """build_recovery_summary: normalizes parsed context."""

    def test_builds_summary_with_full_context(self, patch_env):
        from src.core.utils.superset_context_extractor._base import (
            SupersetContextExtractorBase,
            SupersetParsedContext,
        )

        extractor = SupersetContextExtractorBase(environment=patch_env["env"])
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset_1",
            dataset_id=1,
            dashboard_id=5,
            chart_id=42,
            partial_recovery=False,
            unresolved_references=[],
            imported_filters=[{"col": "status", "val": "active"}],
        )
        summary = extractor.build_recovery_summary(ctx)
        assert summary["dataset_ref"] == "dataset_1"
        assert summary["dataset_id"] == 1
        assert summary["dashboard_id"] == 5
        assert summary["chart_id"] == 42
        assert summary["partial_recovery"] is False
        assert summary["imported_filter_count"] == 1

    def test_builds_summary_with_partial_recovery(self, patch_env):
        from src.core.utils.superset_context_extractor._base import (
            SupersetContextExtractorBase,
            SupersetParsedContext,
        )

        extractor = SupersetContextExtractorBase(environment=patch_env["env"])
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/chart/10",
            dataset_ref="unknown",
            partial_recovery=True,
            unresolved_references=["chart/10"],
        )
        summary = extractor.build_recovery_summary(ctx)
        assert summary["partial_recovery"] is True
        assert summary["unresolved_references"] == ["chart/10"]
        assert summary["imported_filter_count"] == 0


class TestExtractNumericIdentifier:
    """_extract_numeric_identifier: extract int from path."""

    def test_found(self, patch_env):
        from src.core.utils.superset_context_extractor._base import (
            SupersetContextExtractorBase,
        )

        extractor = SupersetContextExtractorBase(environment=patch_env["env"])
        result = extractor._extract_numeric_identifier(
            ["superset", "dashboard", "5", "edit"], "dashboard"
        )
        assert result == 5

    def test_not_found(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_numeric_identifier(
            ["superset", "chart", "10"], "dashboard"
        )
        assert result is None

    def test_not_numeric(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_numeric_identifier(
            ["superset", "dashboard", "my-slug"], "dashboard"
        )
        assert result is None

    def test_no_value_after_resource(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_numeric_identifier(
            ["superset", "dashboard"], "dashboard"
        )
        assert result is None

    def test_empty_path_parts(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_numeric_identifier([], "dashboard")
        assert result is None


class TestExtractDashboardReference:
    """_extract_dashboard_reference: extract id-or-slug."""

    def test_found(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_reference(
            ["superset", "dashboard", "my-dash"]
        )
        assert result == "my-dash"

    def test_dashboard_not_in_path(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_reference(["superset", "chart", "5"])
        assert result is None

    def test_no_value_after_dashboard(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_reference(["superset", "dashboard"])
        assert result is None

    def test_p_is_reserved(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_reference(
            ["superset", "dashboard", "p"]
        )
        assert result is None

    def test_empty_string_candidate(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_reference(
            ["superset", "dashboard", ""]
        )
        assert result is None


class TestExtractDashboardPermalinkKey:
    """_extract_dashboard_permalink_key: extract key from /dashboard/p/<key>/."""

    def test_found(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_permalink_key(
            ["superset", "dashboard", "p", "abc123"]
        )
        assert result == "abc123"

    def test_dashboard_not_in_path(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_permalink_key(["superset", "chart", "5"])
        assert result is None

    def test_not_enough_parts(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_permalink_key(
            ["superset", "dashboard"]
        )
        assert result is None

    def test_wrong_marker(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_permalink_key(
            ["superset", "dashboard", "x", "abc"]
        )
        assert result is None

    def test_empty_key(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_permalink_key(
            ["superset", "dashboard", "p", ""]
        )
        assert result is None


class TestExtractDashboardIdFromState:
    """_extract_dashboard_id_from_state: search nested dict for dashboard ID."""

    def test_found_with_dashboardId(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_id_from_state(
            {"dashboardId": 42, "state": {}}
        )
        assert result == 42

    def test_found_nested(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_id_from_state(
            {"data": {"dashboard_id": 99}}
        )
        assert result == 99

    def test_not_found(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_dashboard_id_from_state(
            {"chartId": 5}
        )
        assert result is None


class TestExtractChartIdFromState:
    """_extract_chart_id_from_state: search nested dict for chart ID."""

    def test_found_with_slice_id(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_chart_id_from_state(
            {"slice_id": 77}
        )
        assert result == 77

    def test_found_nested(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._extract_chart_id_from_state(
            {"state": {"chartId": 88}}
        )
        assert result == 88


class TestSearchNestedNumericKey:
    """_search_nested_numeric_key: recursive ID search."""

    def test_find_in_dict(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._search_nested_numeric_key(
            {"target": 42}, {"target"}
        )
        assert result == 42

    def test_find_in_list(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._search_nested_numeric_key(
            [{"id": 1}, {"target": 99}], {"target"}
        )
        assert result == 99

    def test_deeply_nested(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._search_nested_numeric_key(
            {"a": {"b": {"c": {"chart_id": 55}}}}, {"chart_id"}
        )
        assert result == 55

    def test_not_found(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._search_nested_numeric_key(
            {"a": 1, "b": "text"}, {"target"}
        )
        assert result is None

    def test_none_value_skipped(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._search_nested_numeric_key(
            {"target": None}, {"target"}
        )
        assert result is None

    def test_non_parseable_value_logged(self, patch_env):
        extractor = _make_extractor(patch_env)
        with patch("src.core.utils.superset_context_extractor._base.logger") as mock_log:
            result = extractor._search_nested_numeric_key(
                {"target": "not_a_number"}, {"target"}
            )
            assert result is None
            mock_log.debug.assert_called_once()


class TestDecodeQueryState:
    """_decode_query_state: decode URL query params."""

    def test_parses_native_filters_json(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._decode_query_state(
            {"native_filters": ['{"key": "val"}']}
        )
        assert result["native_filters"] == {"key": "val"}

    def test_parses_form_data_json(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._decode_query_state(
            {"form_data": ['{"datasource": "1__table"}']}
        )
        assert result["form_data"]["datasource"] == "1__table"

    def test_parses_q_param_json(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._decode_query_state(
            {"q": ['{"columns": ["id"]}']}
        )
        assert result["q"]["columns"] == ["id"]

    def test_invalid_json_falls_back_to_raw(self, patch_env):
        extractor = _make_extractor(patch_env)
        with patch("src.core.utils.superset_context_extractor._base.logger") as mock_log:
            result = extractor._decode_query_state(
                {"native_filters": ["{invalid}"]}
            )
            mock_log.explore.assert_called_once()
            # Should be decoded as raw string
            assert result["native_filters"] == "{invalid}"

    def test_regular_param_decoded(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._decode_query_state(
            {"dashboard": ["5"]}
        )
        assert result["dashboard"] == "5"

    def test_empty_values_skipped(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._decode_query_state(
            {"empty": []}
        )
        assert "empty" not in result

    def test_url_decoded(self, patch_env):
        extractor = _make_extractor(patch_env)
        result = extractor._decode_query_state(
            {"name": ["hello%20world"]}
        )
        assert result["name"] == "hello world"


def _make_extractor(patch_env):
    from src.core.utils.superset_context_extractor._base import (
        SupersetContextExtractorBase,
    )

    return SupersetContextExtractorBase(environment=patch_env["env"])
# #endregion Test.SupersetContextExtractorBase
