# #region Test.MigrationArchiveParser [C:3] [TYPE Module] [SEMANTICS test,migration,archive,parser]
# @BRIEF Tests for core/migration/archive_parser.py — MigrationArchiveParser.
# @RELATION BINDS_TO -> [MigrationArchiveParserModule]
# @TEST_EDGE: invalid_yaml_skipped -> logger.reflect called
# @TEST_EDGE: non_dict_payload -> returns None

from pathlib import Path
import sys

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

import json
from pathlib import Path as PathLib
import tempfile
import zipfile
from unittest.mock import MagicMock, patch
import pytest
import yaml


class TestExtractObjectsFromZip:
    """Verify extract_objects_from_zip."""

    def test_happy_path_extracts_all_types(self):
        from src.core.migration.archive_parser import MigrationArchiveParser

        parser = MigrationArchiveParser()

        # Create a temp ZIP with YAML files
        with tempfile.TemporaryDirectory() as tmpdir:
            tmp = PathLib(tmpdir)

            # Create dashboard YAML
            dash_dir = tmp / "dashboards"
            dash_dir.mkdir(parents=True)
            with open(dash_dir / "dash1.yaml", "w") as f:
                yaml.dump({"uuid": "dash-uuid-1", "dashboard_title": "Sales Dashboard"}, f)

            # Create chart YAML
            chart_dir = tmp / "charts"
            chart_dir.mkdir(parents=True)
            with open(chart_dir / "chart1.yaml", "w") as f:
                yaml.dump({"uuid": "chart-uuid-1", "slice_name": "Revenue Chart", "viz_type": "bar"}, f)

            # Create dataset YAML
            ds_dir = tmp / "datasets"
            ds_dir.mkdir(parents=True)
            with open(ds_dir / "ds1.yaml", "w") as f:
                yaml.dump({"uuid": "ds-uuid-1", "table_name": "orders", "schema": "public"}, f)

            # Create ZIP
            zip_path = tmp / "archive.zip"
            with zipfile.ZipFile(zip_path, "w") as zf:
                for file_path in tmp.rglob("*"):
                    if file_path.is_file() and file_path.suffix != ".zip":
                        zf.write(file_path, file_path.relative_to(tmp))

            result = parser.extract_objects_from_zip(str(zip_path))

        assert "dashboards" in result
        assert "charts" in result
        assert "datasets" in result
        assert len(result["dashboards"]) == 1
        assert result["dashboards"][0]["uuid"] == "dash-uuid-1"
        assert len(result["charts"]) == 1
        assert result["charts"][0]["uuid"] == "chart-uuid-1"
        assert len(result["datasets"]) == 1
        assert result["datasets"][0]["uuid"] == "ds-uuid-1"

    def test_empty_zip_returns_empty_lists(self):
        from src.core.migration.archive_parser import MigrationArchiveParser

        parser = MigrationArchiveParser()

        with tempfile.TemporaryDirectory() as tmpdir:
            tmp = PathLib(tmpdir)
            zip_path = tmp / "empty.zip"
            with zipfile.ZipFile(zip_path, "w") as zf:
                pass  # Empty ZIP

            result = parser.extract_objects_from_zip(str(zip_path))

        assert result == {"dashboards": [], "charts": [], "datasets": []}

    def test_nonexistent_file_raises(self):
        from src.core.migration.archive_parser import MigrationArchiveParser

        parser = MigrationArchiveParser()

        with pytest.raises(FileNotFoundError):
            parser.extract_objects_from_zip("/nonexistent/path.zip")


class TestCollectYamlObjects:
    """Verify _collect_yaml_objects."""

    def test_collects_valid_yaml(self):
        from src.core.migration.archive_parser import MigrationArchiveParser

        parser = MigrationArchiveParser()

        with tempfile.TemporaryDirectory() as tmpdir:
            tmp = PathLib(tmpdir)
            dash_dir = tmp / "dashboards"
            dash_dir.mkdir(parents=True)
            with open(dash_dir / "valid.yaml", "w") as f:
                yaml.dump({"uuid": "abc-123", "dashboard_title": "Test"}, f)
            with open(dash_dir / "invalid.yaml", "w") as f:
                f.write("not: valid: yaml: [\n")

            result = parser._collect_yaml_objects(tmp, "dashboards")
            assert len(result) == 1
            assert result[0]["uuid"] == "abc-123"

    def test_skip_invalid_yaml(self):
        from src.core.migration.archive_parser import MigrationArchiveParser

        parser = MigrationArchiveParser()

        with tempfile.TemporaryDirectory() as tmpdir:
            tmp = PathLib(tmpdir)
            dash_dir = tmp / "dashboards"
            dash_dir.mkdir(parents=True)
            with open(dash_dir / "bad.yaml", "w") as f:
                f.write("{{{invalid_yaml\n")

            # Should not raise, should silently skip
            result = parser._collect_yaml_objects(tmp, "dashboards")
            assert result == []


class TestNormalizeObjectPayload:
    """Verify _normalize_object_payload for different object types."""

    @pytest.fixture
    def parser(self):
        from src.core.migration.archive_parser import MigrationArchiveParser

        return MigrationArchiveParser()

    def test_dashboard_normalization(self, parser):
        payload = {
            "uuid": "dash-1",
            "dashboard_title": "Sales Dashboard",
            "slug": "sales",
            "description": "Sales data",
            "owners": [{"id": 1, "username": "admin"}],
        }
        result = parser._normalize_object_payload(payload, "dashboards")
        assert result is not None
        assert result["uuid"] == "dash-1"
        assert result["title"] == "Sales Dashboard"
        assert "signature" in result
        sig = json.loads(result["signature"])
        assert sig["slug"] == "sales"
        assert result["owners"] == [{"id": 1, "username": "admin"}]

    def test_dashboard_no_title_fallback(self, parser):
        payload = {"uuid": "dash-2"}
        result = parser._normalize_object_payload(payload, "dashboards")
        assert result is not None
        assert result["title"] == "Dashboard dash-2"

    def test_chart_normalization(self, parser):
        payload = {
            "uuid": "chart-1",
            "slice_name": "Revenue",
            "viz_type": "bar",
            "params": "{}",
            "datasource_uuid": "ds-1",
        }
        result = parser._normalize_object_payload(payload, "charts")
        assert result is not None
        assert result["uuid"] == "chart-1"
        assert result["title"] == "Revenue"
        assert result["dataset_uuid"] == "ds-1"
        sig = json.loads(result["signature"])
        assert sig["viz_type"] == "bar"

    def test_chart_no_name_fallback(self, parser):
        payload = {"uuid": "chart-2"}
        result = parser._normalize_object_payload(payload, "charts")
        assert result is not None
        assert result["title"] == "Chart chart-2"

    def test_dataset_normalization(self, parser):
        payload = {
            "uuid": "ds-1",
            "table_name": "orders",
            "schema": "public",
            "database_uuid": "db-1",
            "sql": "SELECT * FROM orders",
        }
        result = parser._normalize_object_payload(payload, "datasets")
        assert result is not None
        assert result["uuid"] == "ds-1"
        assert result["title"] == "orders"
        assert result["database_uuid"] == "db-1"
        sig = json.loads(result["signature"])
        assert sig["schema"] == "public"

    def test_dataset_no_name_fallback(self, parser):
        payload = {"uuid": "ds-2", "database_uuid": "db-1"}
        result = parser._normalize_object_payload(payload, "datasets")
        assert result is not None
        assert result["title"] == "Dataset ds-2"

    def test_non_dict_payload_returns_none(self, parser):
        result = parser._normalize_object_payload("not a dict", "dashboards")
        assert result is None

    def test_missing_uuid_returns_none(self, parser):
        result = parser._normalize_object_payload({"dashboard_title": "No UUID"}, "dashboards")
        assert result is None

    def test_unknown_type_returns_none(self, parser):
        payload = {"uuid": "unknown-1", "name": "Something"}
        result = parser._normalize_object_payload(payload, "unknown_type")
        assert result is None

    def test_chart_with_dataset_uuid_alias(self, parser):
        payload = {"uuid": "chart-3", "slice_name": "Test", "dataset_uuid": "ds-alias"}
        result = parser._normalize_object_payload(payload, "charts")
        assert result is not None
        assert result["dataset_uuid"] == "ds-alias"

    def test_dashboard_owners_none_becomes_empty(self, parser):
        payload = {"uuid": "dash-3", "dashboard_title": "Test"}
        result = parser._normalize_object_payload(payload, "dashboards")
        assert result is not None
        assert result["owners"] == []
# #endregion Test.MigrationArchiveParser
