import os
import types

from PySide2.QtCore import Qt
from PySide2.QtWidgets import QApplication


os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")


def _get_qapp():
    """Return an available Qt application instance for widget tests."""
    app = QApplication.instance()
    if app:
        return app
    return QApplication([])


def test_batch_tools_package_importable():
    import easyams as ams

    assert hasattr(ams, "batch_tools")


def test_remove_items_dialog_defaults_unchecked(monkeypatch):
    from easyams.batch_tools.remove_items import BatchRemoveItemsDialog

    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.resolve_dialog_capabilities",
        lambda: {"Cameras": True, "Markers": True, "Scale Bars": True},
    )
    _get_qapp()
    dialog = BatchRemoveItemsDialog(parent=None)

    assert dialog.item_list.count() > 0
    assert dialog.get_selected_item_types() == []
    assert dialog.get_selected_chunk_keys() == []


def test_unsupported_item_types_are_disabled_by_version(monkeypatch):
    from easyams.batch_tools.remove_items import BatchRemoveItemsDialog

    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.get_metashape_version",
        lambda: "2.1.2",
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.resolve_dialog_capabilities",
        lambda: {
            "Cameras": True,
            "Markers": True,
            "Scale Bars": True,
            "Depth Maps": False,
            "Point Clouds": False,
            "Models": False,
        },
    )
    _get_qapp()
    dialog = BatchRemoveItemsDialog(parent=None)

    assert dialog.is_item_type_enabled("Depth Maps") is False
    assert dialog.is_item_type_enabled("Point Clouds") is False
    assert dialog.is_item_type_enabled("Models") is False


def test_loads_builtin_api_capability():
    from easyams.api_capability import load_api_capability

    matrix = load_api_capability()

    assert "versions" in matrix
    assert "2.2.1" in matrix["versions"]


def test_runs_local_probe_when_current_version_not_synced(monkeypatch):
    from easyams.api_capability import resolve_capabilities_for_current_version

    monkeypatch.setattr(
        "easyams.api_capability.get_metashape_version",
        lambda: "2.2.1",
    )
    monkeypatch.setattr(
        "easyams.api_capability.read_runtime_sync_state",
        lambda: {"2.2.1": {"synced": False}},
    )
    monkeypatch.setattr(
        "easyams.api_capability.write_runtime_sync_state",
        lambda _state: None,
    )

    called = {"probe": 0}

    def _probe(_version, base_caps):
        called["probe"] += 1
        return base_caps

    monkeypatch.setattr("easyams.api_capability.probe_api_capabilities", _probe)
    resolve_capabilities_for_current_version()

    assert called["probe"] == 1


def test_unknown_new_version_clones_nearest_then_probes(monkeypatch):
    from easyams.api_capability import resolve_capabilities_for_current_version

    monkeypatch.setattr(
        "easyams.api_capability.get_metashape_version",
        lambda: "2.3.0",
    )
    monkeypatch.setattr(
        "easyams.api_capability.read_runtime_sync_state",
        lambda: {},
    )
    monkeypatch.setattr(
        "easyams.api_capability.write_runtime_sync_state",
        lambda _state: None,
    )
    caps = resolve_capabilities_for_current_version()

    assert caps["_source_version"] == "2.2.1"
    assert caps["_probed"] is True


def test_batch_import_module_removed():
    import importlib

    try:
        importlib.import_module("easyams.batch_import")
    except ModuleNotFoundError:
        assert True
        return
    raise AssertionError("easyams.batch_import should be removed")


def test_batch_tools_modules_do_not_backref_batch_import():
    from easyams import batch_tools

    assert hasattr(batch_tools.images, "_legacy_images") is False
    assert hasattr(batch_tools.masks, "_legacy_masks") is False
    assert hasattr(batch_tools.markers, "_legacy_markers") is False


def test_remove_items_only_shows_existing_types(monkeypatch):
    from easyams.batch_tools.remove_items import BatchRemoveItemsDialog

    class _Chunk:
        def __init__(self, key, label):
            self.key = key
            self.label = label
            self.cameras = [1]
            self.markers = [1, 2]
            self.scalebars = []
            self.shapes = []
            self.depth_maps = None
            self.point_cloud = None
            self.model = None
            self.tiled_model = None
            self.elevation = None
            self.orthomosaic = None
            self.tie_points = None

    fake_doc = types.SimpleNamespace(chunks=[_Chunk(1, "Chunk A")])
    fake_app = types.SimpleNamespace(document=fake_doc)

    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.Metashape",
        types.SimpleNamespace(app=fake_app),
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.resolve_dialog_capabilities",
        lambda: {"Cameras": True, "Markers": True},
    )

    _get_qapp()
    dialog = BatchRemoveItemsDialog(parent=None)
    item_names = [
        dialog.item_list.item(index).text() for index in range(dialog.item_list.count())
    ]

    assert item_names == ["Cameras", "Markers"]


def test_chunk_tree_only_shows_chunk_existing_types(monkeypatch):
    from easyams.batch_tools.remove_items import BatchRemoveItemsDialog

    class _Chunk:
        def __init__(self, key, label, has_model):
            self.key = key
            self.label = label
            self.cameras = [1]
            self.markers = []
            self.scalebars = []
            self.shapes = []
            self.depth_maps = None
            self.point_cloud = None
            self.model = object() if has_model else None
            self.tiled_model = None
            self.elevation = None
            self.orthomosaic = None
            self.tie_points = None

    chunks = [_Chunk(1, "Chunk A", False), _Chunk(2, "Chunk B", True)]
    fake_doc = types.SimpleNamespace(chunks=chunks)
    fake_app = types.SimpleNamespace(document=fake_doc)

    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.Metashape",
        types.SimpleNamespace(app=fake_app),
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.resolve_dialog_capabilities",
        lambda: {"Cameras": True, "Models": True},
    )

    _get_qapp()
    dialog = BatchRemoveItemsDialog(parent=None)

    root = dialog.tree_widget.invisibleRootItem()
    first_chunk = root.child(0)
    second_chunk = root.child(1)
    first_children = [
        first_chunk.child(i).text(0) for i in range(first_chunk.childCount())
    ]
    second_children = [
        second_chunk.child(i).text(0) for i in range(second_chunk.childCount())
    ]

    assert first_children == ["Cameras (1)"]
    assert second_children == ["Cameras (1)", "Models (1)"]


def test_preview_updates_to_red_strike_for_selected_deletions(monkeypatch):
    from easyams.batch_tools.remove_items import BatchRemoveItemsDialog

    class _Chunk:
        def __init__(self):
            self.key = 1
            self.label = "Chunk A"
            self.cameras = [1]
            self.markers = [1]
            self.scalebars = []
            self.shapes = []
            self.depth_maps = None
            self.point_cloud = None
            self.model = None
            self.tiled_model = None
            self.elevation = None
            self.orthomosaic = None
            self.tie_points = None

    fake_doc = types.SimpleNamespace(chunks=[_Chunk()])
    fake_app = types.SimpleNamespace(document=fake_doc)

    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.Metashape",
        types.SimpleNamespace(app=fake_app),
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.resolve_dialog_capabilities",
        lambda: {"Cameras": True, "Markers": True},
    )

    _get_qapp()
    dialog = BatchRemoveItemsDialog(parent=None)

    root = dialog.tree_widget.invisibleRootItem()
    chunk_item = root.child(0)
    chunk_item.setCheckState(0, Qt.CheckState.Checked)

    for index in range(dialog.item_list.count()):
        item = dialog.item_list.item(index)
        if item.text() == "Cameras":
            item.setSelected(True)

    camera_node = chunk_item.child(0)
    assert camera_node.font(0).strikeOut() is True


def test_preview_labels_follow_type_count_format():
    from easyams.batch_tools.remove_items import build_preview_for_chunk

    chunk = types.SimpleNamespace(
        cameras=[1, 2],
        markers=[1, 2, 3],
        scalebars=[],
        shapes=[],
        depth_maps=None,
        point_cloud=object(),
        model=None,
        tiled_model=None,
        elevation=None,
        orthomosaic=None,
        tie_points=object(),
    )

    preview = build_preview_for_chunk(chunk)

    assert "Cameras (2)" in preview
    assert "Markers (3)" in preview
    assert "Point Clouds (1)" in preview
    assert "Tie Points (1)" in preview


def test_deletion_cameras_also_removes_groups():
    from easyams.batch_tools.remove_items import apply_type_deletion

    chunk = types.SimpleNamespace(cameras=[1, 2], camera_groups=[1], model=None)
    apply_type_deletion(chunk, ["Cameras"])

    assert len(chunk.cameras) == 0
    assert len(chunk.camera_groups) == 0


def test_deletion_cameras_prefers_chunk_remove_api():
    from easyams.batch_tools.remove_items import apply_type_deletion

    removed = []

    class _Chunk:
        def __init__(self):
            self.cameras = ("c1", "c2")
            self.camera_groups = ("g1",)

        def remove(self, items):
            removed.append(list(items))

    chunk = _Chunk()
    apply_type_deletion(chunk, ["Cameras"])

    assert removed == [["c1", "c2"], ["g1"]]


def test_deletion_heavy_assets_type_based():
    from easyams.batch_tools.remove_items import apply_type_deletion

    chunk = types.SimpleNamespace(
        model=object(),
        elevation=object(),
        orthomosaic=object(),
    )
    apply_type_deletion(chunk, ["Models", "Elevation Models", "Orthomosaics"])

    assert chunk.model is None
    assert chunk.elevation is None
    assert chunk.orthomosaic is None


def test_deletion_heavy_assets_prefers_chunk_remove_api():
    from easyams.batch_tools.remove_items import apply_type_deletion

    removed = []
    model = object()
    elevation = object()
    orthomosaic = object()

    class _Chunk:
        def __init__(self):
            self.model = model
            self.elevation = elevation
            self.orthomosaic = orthomosaic

        def remove(self, item):
            removed.append(item)

    chunk = _Chunk()
    apply_type_deletion(chunk, ["Models", "Elevation Models", "Orthomosaics"])

    assert removed == [model, elevation, orthomosaic]


def test_deletion_scalebars_prefers_chunk_remove_api():
    from easyams.batch_tools.remove_items import apply_type_deletion

    removed = []

    class _Chunk:
        def __init__(self):
            self.scalebars = ("sb1", "sb2")

        def remove(self, items):
            removed.append(list(items))

    chunk = _Chunk()
    apply_type_deletion(chunk, ["Scale Bars"])

    assert removed == [["sb1", "sb2"]]


def test_deletion_shapes_uses_shapes_remove_api():
    from easyams.batch_tools.remove_items import apply_type_deletion

    called = {"remove": 0}

    class _Shapes:
        def __init__(self):
            self.shapes = ["s1", "s2"]
            self.groups = ["g1"]

        def remove(self, items):
            called["remove"] += 1
            assert len(items) == 3

    chunk = types.SimpleNamespace(shapes=_Shapes())
    apply_type_deletion(chunk, ["Shapes"])

    assert called["remove"] == 1


def test_deletion_shapes_attempts_remove_layer_container():
    from easyams.batch_tools.remove_items import apply_type_deletion

    called = {"layer_remove": 0}

    class _Shapes:
        def __init__(self):
            self.shapes = ["s1"]
            self.groups = []

        def remove(self, _items):
            return None

    class _Chunk:
        def __init__(self):
            self.shapes = _Shapes()

        def remove(self, _items):
            called["layer_remove"] += 1

    chunk = _Chunk()
    apply_type_deletion(chunk, ["Shapes"])

    assert called["layer_remove"] == 1


def test_deletion_shapes_removes_by_group_then_group_nodes():
    from easyams.batch_tools.remove_items import apply_type_deletion

    removed = []

    class _Group:
        def __init__(self, name):
            self.name = name

        def __repr__(self):
            return f"Group({self.name})"

    class _Shape:
        def __init__(self, name, group):
            self.name = name
            self.group = group

        def __repr__(self):
            return f"Shape({self.name})"

    g1 = _Group("g1")
    g2 = _Group("g2")

    class _Shapes:
        def __init__(self):
            self.groups = [g1, g2]
            self._items = [
                _Shape("s1", g1),
                _Shape("s2", g1),
                _Shape("s3", g2),
            ]

        def __iter__(self):
            return iter(self._items)

        def remove(self, item):
            removed.append(item)
            if item in self.groups:
                self.groups.remove(item)
                return
            if item in self._items:
                self._items.remove(item)

    chunk = types.SimpleNamespace(shapes=_Shapes())
    apply_type_deletion(chunk, ["Shapes"])

    assert g1 in removed
    assert g2 in removed


def test_confirm_execute_requires_explicit_yes(monkeypatch):
    from easyams.batch_tools.remove_items import BatchRemoveItemsDialog

    class _Chunk:
        def __init__(self):
            self.key = 1
            self.label = "Chunk A"
            self.cameras = [1]
            self.markers = []
            self.scalebars = []
            self.shapes = []
            self.depth_maps = None
            self.point_cloud = None
            self.model = None
            self.tiled_model = None
            self.elevation = None
            self.orthomosaic = None
            self.tie_points = None

    fake_doc = types.SimpleNamespace(chunks=[_Chunk()])
    fake_app = types.SimpleNamespace(document=fake_doc)
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.Metashape",
        types.SimpleNamespace(app=fake_app),
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.resolve_dialog_capabilities",
        lambda: {"Cameras": True},
    )

    _get_qapp()
    dialog = BatchRemoveItemsDialog(parent=None)

    root = dialog.tree_widget.invisibleRootItem()
    chunk_item = root.child(0)
    chunk_item.setCheckState(0, Qt.CheckState.Checked)
    dialog.item_list.item(0).setSelected(True)

    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.QMessageBox.question",
        lambda *_args, **_kwargs: 16384,
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.QMessageBox.information",
        lambda *_args, **_kwargs: None,
    )
    assert dialog.confirm_execution() is True


def test_confirmation_message_contains_red_shapes_warning():
    from easyams.batch_tools.remove_items import build_confirmation_message

    message = build_confirmation_message([1], ["Shapes"])

    assert "color:#cc2222" in message
    assert "default empty shape layer" in message.lower()


def test_execute_does_not_call_document_save(monkeypatch):
    from easyams.batch_tools.remove_items import BatchRemoveItemsDialog

    called = {"save": 0}

    class _Chunk:
        def __init__(self):
            self.key = 1
            self.label = "Chunk A"
            self.cameras = [1]
            self.camera_groups = []
            self.markers = []
            self.scalebars = []
            self.shapes = []
            self.depth_maps = None
            self.point_cloud = None
            self.model = None
            self.tiled_model = None
            self.elevation = None
            self.orthomosaic = None
            self.tie_points = None

    class _Doc:
        def __init__(self):
            self.chunks = [_Chunk()]

        def save(self):
            called["save"] += 1

    fake_doc = _Doc()
    fake_app = types.SimpleNamespace(document=fake_doc)
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.Metashape",
        types.SimpleNamespace(app=fake_app),
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.resolve_dialog_capabilities",
        lambda: {"Cameras": True},
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.QMessageBox.question",
        lambda *_args, **_kwargs: 16384,
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.QMessageBox.information",
        lambda *_args, **_kwargs: None,
    )

    _get_qapp()
    dialog = BatchRemoveItemsDialog(parent=None)
    root = dialog.tree_widget.invisibleRootItem()
    chunk_item = root.child(0)
    chunk_item.setCheckState(0, Qt.CheckState.Checked)
    dialog.item_list.item(0).setSelected(True)

    dialog.execute_remove()

    assert called["save"] == 0


def test_execute_shows_result_dialog(monkeypatch):
    from easyams.batch_tools.remove_items import BatchRemoveItemsDialog

    shown = {"info": 0}

    class _Chunk:
        def __init__(self):
            self.key = 1
            self.label = "Chunk A"
            self.cameras = [1]
            self.camera_groups = []
            self.markers = []
            self.scalebars = []
            self.shapes = []
            self.depth_maps = None
            self.point_cloud = None
            self.model = None
            self.tiled_model = None
            self.elevation = None
            self.orthomosaic = None
            self.tie_points = None

    fake_doc = types.SimpleNamespace(chunks=[_Chunk()])
    fake_app = types.SimpleNamespace(document=fake_doc)
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.Metashape",
        types.SimpleNamespace(app=fake_app),
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.resolve_dialog_capabilities",
        lambda: {"Cameras": True},
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.QMessageBox.question",
        lambda *_args, **_kwargs: 16384,
    )
    monkeypatch.setattr(
        "easyams.batch_tools.remove_items.QMessageBox.information",
        lambda *_args, **_kwargs: shown.__setitem__("info", shown["info"] + 1),
    )

    _get_qapp()
    dialog = BatchRemoveItemsDialog(parent=None)
    root = dialog.tree_widget.invisibleRootItem()
    chunk_item = root.child(0)
    chunk_item.setCheckState(0, Qt.CheckState.Checked)
    dialog.item_list.item(0).setSelected(True)

    dialog.execute_remove()

    assert shown["info"] == 1
