"""Tests for ifcurl.diff — step ID extraction and entity expansion."""

from __future__ import annotations

import pytest

from ifcurl.diff import expand_step_ids, step_ids_from_diff

# ---------------------------------------------------------------------------
# step_ids_from_diff
# ---------------------------------------------------------------------------

SAMPLE_DIFF = """\
diff --git a/model.ifc b/model.ifc
--- a/model.ifc
+++ b/model.ifc
@@ -1,5 +1,5 @@
 #1=IFCPROJECT('abc');
-#10=IFCWALL('old_wall');
-#11=IFCWALLSTANDARDCASE('deleted_wall');
+#10=IFCWALL('new_wall');
+#99=IFCWALLSTANDARDCASE('added_wall');
 #20=IFCSLAB('slab');
"""


class TestStepIdsFromDiff:
    def test_modified_id_in_both_sets(self):
        ids = step_ids_from_diff(SAMPLE_DIFF)
        assert 10 in ids["modified"]

    def test_added_id_only_in_inserted(self):
        ids = step_ids_from_diff(SAMPLE_DIFF)
        assert 99 in ids["added"]

    def test_removed_id_only_in_deleted(self):
        ids = step_ids_from_diff(SAMPLE_DIFF)
        assert 11 in ids["removed"]

    def test_unchanged_id_absent(self):
        ids = step_ids_from_diff(SAMPLE_DIFF)
        assert 1 not in ids["added"]
        assert 1 not in ids["modified"]
        assert 1 not in ids["removed"]
        assert 20 not in ids["added"]
        assert 20 not in ids["modified"]
        assert 20 not in ids["removed"]

    def test_modified_not_in_added_or_removed(self):
        ids = step_ids_from_diff(SAMPLE_DIFF)
        assert 10 not in ids["added"]
        assert 10 not in ids["removed"]

    def test_empty_diff(self):
        ids = step_ids_from_diff("")
        assert ids == {"added": set(), "modified": set(), "removed": set()}

    def test_context_lines_ignored(self):
        diff = " #5=IFCSITE('site');\n"
        ids = step_ids_from_diff(diff)
        assert (
            5 not in ids["added"]
            and 5 not in ids["removed"]
            and 5 not in ids["modified"]
        )

    def test_git_header_lines_ignored(self):
        diff = "--- a/model.ifc\n+++ b/model.ifc\n"
        ids = step_ids_from_diff(diff)
        assert ids == {"added": set(), "modified": set(), "removed": set()}


# ---------------------------------------------------------------------------
# expand_step_ids — requires a real ifcopenshell model
# ---------------------------------------------------------------------------


class TestExpandStepIds:
    def test_product_promoted_to_modified(self, model_with_geometry):
        """An IfcProduct step ID in 'modified' stays in modified after expansion."""
        # Find a real product step ID in the model
        products = list(model_with_geometry.by_type("IfcProduct"))
        assert products, "fixture must contain at least one IfcProduct"
        pid = products[0].id()

        raw = {"added": set(), "modified": {pid}, "removed": set()}
        expanded = expand_step_ids(model_with_geometry, raw)

        assert pid in expanded["modified"]

    def test_added_ids_not_moved_to_modified(self, model_with_geometry):
        """IDs in 'added' must not be demoted into 'modified' by expansion."""
        products = list(model_with_geometry.by_type("IfcProduct"))
        assert products
        pid = products[0].id()

        raw = {"added": {pid}, "modified": set(), "removed": set()}
        expanded = expand_step_ids(model_with_geometry, raw)

        assert pid in expanded["added"]
        assert pid not in expanded["modified"]

    def test_removed_ids_unchanged(self, model_with_geometry):
        """The 'removed' set is never altered by expansion."""
        raw = {"added": set(), "modified": set(), "removed": {9999}}
        expanded = expand_step_ids(model_with_geometry, raw)
        assert expanded["removed"] == {9999}

    def test_nonexistent_id_silently_skipped(self, model_with_geometry):
        """Step IDs absent from the model don't cause errors; they pass through unchanged."""
        raw = {"added": set(), "modified": {99999999}, "removed": set()}
        expanded = expand_step_ids(model_with_geometry, raw)
        # The ID passes through unmodified — expand adds no extra products for it
        assert expanded["modified"] == {99999999}

    def test_shape_representation_expands_to_product(self, model_with_geometry):
        """A changed IfcShapeRepresentation expands to its parent IfcProduct."""
        reps = list(model_with_geometry.by_type("IfcShapeRepresentation"))
        if not reps:
            pytest.skip("no IfcShapeRepresentation in fixture")

        rep = reps[0]
        products_via_rep = set()
        for prod_rep in rep.OfProductRepresentation:
            for product in prod_rep.ShapeOfProduct:
                products_via_rep.add(product.id())

        if not products_via_rep:
            pytest.skip("representation not linked to any product in fixture")

        raw = {"added": set(), "modified": {rep.id()}, "removed": set()}
        expanded = expand_step_ids(model_with_geometry, raw)

        for pid in products_via_rep:
            assert pid in expanded["modified"]
