# #region Test.SupersetContextRecovery [C:3] [TYPE Module] [SEMANTICS test, superset, recovery, filter, dashboard]
# @BRIEF Tests for _recovery.py — SupersetContextRecoveryMixin recover_imported_filters and _normalize_imported_filter_payload.
# @RELATION BINDS_TO -> [SupersetContextRecoveryMixin]
# @TEST_CONTRACT: [SupersetParsedContext] -> [List[Dict]]
# @TEST_SCENARIO: dashboard_with_native_filters -> recovers from json_metadata
# @TEST_SCENARIO: no_dashboard_id -> URL filters only
# @TEST_SCENARIO: empty_recovered -> fallback entry
# @TEST_EDGE: json_metadata_as_string -> parsed correctly
# @TEST_EDGE: default_filters_as_string -> parsed correctly
# @TEST_EDGE: metadata_fetch_exception -> fallback to URL filters

from __future__ import annotations

import sys
from pathlib import Path

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

import json
import os

os.environ.setdefault("SECRET_KEY", "test-secret-key-for-testing")
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")

from unittest.mock import MagicMock, patch

import pytest

from src.core.utils.superset_context_extractor._base import SupersetParsedContext
from src.core.utils.superset_context_extractor._recovery import (
    SupersetContextRecoveryMixin,
)


class _TestExtractor(SupersetContextRecoveryMixin):
    """Concrete test class for the recovery mixin."""
    pass


@pytest.fixture
def extractor():
    ext = _TestExtractor()
    ext.client = MagicMock()
    return ext


# ──────────────────────────────────────────────
# _normalize_imported_filter_payload
# ──────────────────────────────────────────────


class TestNormalizeImportedFilterPayload:
    """_normalize_imported_filter_payload — normalize one filter entry."""

    def test_minimal_payload(self, extractor):
        """Minimal payload uses defaults."""
        result = extractor._normalize_imported_filter_payload(
            {"filter_name": "country"},
            default_source="superset_url",
            default_note="From URL",
        )
        assert result["filter_name"] == "country"
        assert result["source"] == "superset_url"
        assert result["notes"] == "From URL"
        assert result["raw_value"] is None
        assert result["recovery_status"] == "partial"
        assert result["requires_confirmation"] is True
        assert result["confidence_state"] == "unresolved"

    def test_full_payload(self, extractor):
        """Full payload preserves values."""
        result = extractor._normalize_imported_filter_payload(
            {
                "filter_name": "  country  ",
                "display_name": "Country",
                "raw_value": "US",
                "source": "superset_permalink",
                "recovery_status": "recovered",
                "requires_confirmation": False,
                "notes": "From permalink",
            },
            default_source="superset_url",
            default_note="Fallback",
        )
        assert result["filter_name"] == "country"  # stripped
        assert result["display_name"] == "Country"
        assert result["raw_value"] == "US"
        assert result["recovery_status"] == "recovered"
        assert result["requires_confirmation"] is False
        assert result["confidence_state"] == "imported"

    def test_value_fallback(self, extractor):
        """'value' key used when 'raw_value' absent."""
        result = extractor._normalize_imported_filter_payload(
            {"filter_name": "c", "value": "fallback"},
            default_source="url",
            default_note="note",
        )
        assert result["raw_value"] == "fallback"

    def test_no_name_fallback(self, extractor):
        """Missing filter_name uses 'unresolved_filter'."""
        result = extractor._normalize_imported_filter_payload(
            {},
            default_source="url",
            default_note="note",
        )
        assert result["filter_name"] == "unresolved_filter"


# ──────────────────────────────────────────────
# recover_imported_filters — no dashboard context
# ──────────────────────────────────────────────


class TestRecoverImportedFiltersNoDashboard:
    """recover_imported_filters — when dashboard_id is None."""

    def test_no_dashboard_id_url_filters_only(self, extractor):
        """Filters from URL state are recovered without dashboard enrichment."""
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/chart/42",
            dataset_ref="chart:42",
            chart_id=42,
            resource_type="chart",
            partial_recovery=True,
            unresolved_references=["chart_dataset_binding_unresolved"],
            imported_filters=[
                {
                    "filter_name": "country",
                    "raw_value": "US",
                    "source": "superset_url",
                }
            ],
        )
        result = extractor.recover_imported_filters(ctx)
        assert len(result) == 1
        assert result[0]["filter_name"] == "country"
        assert result[0]["raw_value"] == "US"

    def test_empty_url_filters_returns_empty(self, extractor):
        """No URL filters returns empty list when no dashboard."""
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/chart/42",
            dataset_ref="chart:42",
            chart_id=42,
            resource_type="chart",
        )
        result = extractor.recover_imported_filters(ctx)
        assert result == []


# ──────────────────────────────────────────────
# recover_imported_filters — with dashboard context
# ──────────────────────────────────────────────


class TestRecoverImportedFiltersWithDashboard:
    """recover_imported_filters — with dashboard_id, enriched from metadata."""

    # ── Helpers ──────────────────────────────

    def _make_dashboard_payload(
        self,
        native_filter_configuration=None,
        default_filters=None,
    ) -> dict:
        meta = {}
        if native_filter_configuration is not None:
            meta["native_filter_configuration"] = native_filter_configuration
        if default_filters is not None:
            meta["default_filters"] = default_filters
        payload = {"json_metadata": json.dumps(meta) if meta else "{}"}
        return {"result": payload}

    # ── Tests ────────────────────────────────

    def test_dashboard_recovers_native_filters(self, extractor):
        """Dashboard with native filter configuration recovers filter entries."""
        extractor.client.get_dashboard = MagicMock(
            return_value=self._make_dashboard_payload(
                native_filter_configuration=[
                    {
                        "name": "country_filter",
                        "label": "Country",
                        "id": "NATIVE-1",
                    }
                ],
                default_filters={"country_filter": "US"},
            )
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
            resource_type="dashboard",
        )
        result = extractor.recover_imported_filters(ctx)
        assert len(result) == 1
        assert result[0]["filter_name"] == "country_filter"
        assert result[0]["display_name"] == "Country"
        assert result[0]["raw_value"] == "US"
        assert result[0]["recovery_status"] == "recovered"

    def test_native_filter_partial_no_default(self, extractor):
        """Native filter without default value gets partial status."""
        extractor.client.get_dashboard = MagicMock(
            return_value=self._make_dashboard_payload(
                native_filter_configuration=[
                    {"name": "year_filter", "label": "Year"}
                ],
            )
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
        )
        result = extractor.recover_imported_filters(ctx)
        assert len(result) == 1
        assert result[0]["recovery_status"] == "partial"
        assert result[0]["requires_confirmation"] is True

    def test_native_filters_merged_with_url_filters(self, extractor):
        """URL-imported filters merge with dashboard metadata."""
        extractor.client.get_dashboard = MagicMock(
            return_value=self._make_dashboard_payload(
                native_filter_configuration=[
                    {"name": "country_filter", "label": "Country", "id": "NATIVE-1"}
                ],
                default_filters={"country_filter": "US"},
            )
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
            imported_filters=[
                {
                    "filter_name": "NATIVE-1",
                    "raw_value": None,
                    "source": "superset_url",
                }
            ],
        )
        result = extractor.recover_imported_filters(ctx)
        # Should be 1 entry after merge
        assert len(result) == 1
        # Filter name should be canonicalized from metadata
        assert result[0]["filter_name"] == "country_filter"

    def test_default_filters_as_string_parsed(self, extractor):
        """default_filters JSON string is parsed."""
        extractor.client.get_dashboard = MagicMock(
            return_value=self._make_dashboard_payload(
                native_filter_configuration=[
                    {"name": "f1", "id": "N-1"}
                ],
                default_filters='{"f1": "val1"}',
            )
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
        )
        result = extractor.recover_imported_filters(ctx)
        assert result[0]["raw_value"] == "val1"

    def test_default_filters_invalid_json_logged(self, extractor):
        """Invalid default_filters JSON is caught and logged."""
        extractor.client.get_dashboard = MagicMock(
            return_value=self._make_dashboard_payload(
                native_filter_configuration=[
                    {"name": "f1"}
                ],
                default_filters="{invalid}",
            )
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
        )
        result = extractor.recover_imported_filters(ctx)
        # Should not crash, filter has no default
        assert result[0]["raw_value"] is None

    def test_get_dashboard_failure_falls_back(self, extractor):
        """Dashboard fetch failure falls back to URL filters only."""
        extractor.client.get_dashboard = MagicMock(
            side_effect=Exception("Dashboard API error")
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
            imported_filters=[
                {
                    "filter_name": "country",
                    "raw_value": "US",
                    "source": "superset_url",
                }
            ],
        )
        result = extractor.recover_imported_filters(ctx)
        assert len(result) == 1
        assert result[0]["filter_name"] == "country"

    def test_empty_recovered_filters_adds_fallback(self, extractor):
        """No recovered filters adds a fallback entry."""
        extractor.client.get_dashboard = MagicMock(
            return_value=self._make_dashboard_payload(
                native_filter_configuration=[],
            )
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
        )
        result = extractor.recover_imported_filters(ctx)
        assert len(result) == 1
        assert result[0]["recovery_status"] == "partial"
        assert "dashboard_5_filters" in result[0]["filter_name"]

    def test_non_dict_item_in_native_config_skipped(self, extractor):
        """Non-dict items in native_filter_configuration are skipped."""
        extractor.client.get_dashboard = MagicMock(
            return_value=self._make_dashboard_payload(
                native_filter_configuration=[
                    "not_a_dict",
                    {"name": "valid_filter"},
                ],
            )
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
        )
        result = extractor.recover_imported_filters(ctx)
        assert len(result) == 1
        assert result[0]["filter_name"] == "valid_filter"

    def test_json_metadata_as_string_parsed(self, extractor):
        """json_metadata as string is parsed (not dict)."""
        extractor.client.get_dashboard = MagicMock(
            return_value={
                "result": {
                    "json_metadata": json.dumps(
                        {
                            "native_filter_configuration": [
                                {"name": "f1"}
                            ]
                        }
                    )
                }
            }
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
        )
        result = extractor.recover_imported_filters(ctx)
        assert len(result) == 1
        assert result[0]["filter_name"] == "f1"

    def test_empty_filter_name_skipped(self, extractor):
        """Filter with empty name in native configuration is skipped."""
        extractor.client.get_dashboard = MagicMock(
            return_value=self._make_dashboard_payload(
                native_filter_configuration=[
                    {"name": ""},
                    {"name": "valid_filter"},
                ],
            )
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
        )
        result = extractor.recover_imported_filters(ctx)
        assert len(result) == 1
        assert result[0]["filter_name"] == "valid_filter"


# ──────────────────────────────────────────────
# merge_recovered_filter edge cases
# ──────────────────────────────────────────────


class TestRecoveredFilterMerge:
    """Internal merge_recovered_filter behavior via recover_imported_filters."""

    def test_url_filter_upgraded_by_metadata(self, extractor):
        """URL filter gets display_name from metadata when missing."""
        extractor.client.get_dashboard = MagicMock(
            return_value={
                "result": {
                    "json_metadata": json.dumps(
                        {
                            "native_filter_configuration": [
                                {
                                    "name": "country_filter",
                                    "label": "Country",
                                    "id": "NATIVE-1",
                                }
                            ],
                            "default_filters": {"country_filter": "US"},
                        }
                    )
                }
            }
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
            imported_filters=[
                {
                    "filter_name": "NATIVE-1",
                    "raw_value": None,
                    "display_name": None,
                    "source": "superset_url",
                }
            ],
        )
        result = extractor.recover_imported_filters(ctx)
        assert result[0]["display_name"] == "Country"
        assert result[0]["filter_name"] == "country_filter"

    def test_display_name_merged_from_metadata(self, extractor):
        """Existing filter without display_name gets it from metadata merge (line 60)."""
        extractor.client.get_dashboard = MagicMock(
            return_value={
                "result": {
                    "json_metadata": json.dumps(
                        {
                            "native_filter_configuration": [
                                {"name": "country", "label": "Country Label"}
                            ],
                            "default_filters": {"country": "US"},
                        }
                    )
                }
            }
        )
        # URL filter with display_name=None, metadata has the label
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
            imported_filters=[
                {
                    "filter_name": "country",
                    "raw_value": None,
                    "display_name": None,
                    "source": "superset_url",
                }
            ],
        )
        result = extractor.recover_imported_filters(ctx)
        # Metadata filter with display_name should enrich the existing entry
        entry = next(f for f in result if f["filter_name"] == "country")
        assert entry["display_name"] == "Country Label"

    def test_normalized_value_merged_from_metadata(self, extractor):
        """Existing filter without normalized_value gets it from metadata merge (line 82)."""
        extractor.client.get_dashboard = MagicMock(
            return_value={
                "result": {
                    "json_metadata": json.dumps(
                        {
                            "native_filter_configuration": [
                                {"name": "status", "id": "N-1"}
                            ],
                            "default_filters": {"status": "active"},
                        }
                    )
                }
            }
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
            imported_filters=[
                {
                    "filter_name": "N-1",
                    "raw_value": None,
                    "display_name": None,
                    "source": "superset_url",
                }
            ],
        )
        result = extractor.recover_imported_filters(ctx)
        # Metadata filter merges — after canonicalization "N-1" becomes "status"
        status = next(f for f in result if f["filter_name"] == "status")
        # The metadata filter should have been merged with URL entry
        assert status["display_name"] is not None

    def test_json_metadata_non_dict_fallback(self, extractor):
        """json_metadata that is not a dict is reset to empty (line 105)."""
        extractor.client.get_dashboard = MagicMock(
            return_value={
                "result": {
                    "json_metadata": 42  # Not a dict or string
                }
            }
        )
        ctx = SupersetParsedContext(
            source_url="https://superset.example.com/superset/dashboard/5",
            dataset_ref="dataset:10",
            dataset_id=10,
            dashboard_id=5,
        )
        result = extractor.recover_imported_filters(ctx)
        # Should not crash, should add fallback since no native filters found
        assert len(result) == 1
        assert result[0]["recovery_status"] == "partial"
# #endregion Test.SupersetContextRecovery
