#  Copyright 2026 European Union
#  Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
#  SPDX-License-Identifier: EUPL-1.2
"""Unit tests for the ProcessorPipelineModel."""

import pytest

# Skip this module if PySide6 is not available
pytest.importorskip('PySide6', reason='PySide6 is required for GUI tests')
from PySide6.QtCore import QModelIndex, Qt

from mafw.steering.models import GroupConfig, ProcessorConfig
from mafw.steering_gui.models.pipeline import PipelineItem, ProcessorPipeline
from mafw.steering_gui.models.processor_pipeline_model import PipelineRoles, ProcessorPipelineModel

pytestmark = pytest.mark.gui


@pytest.fixture
def sample_pipeline() -> ProcessorPipeline:
    """Create a sample pipeline with groups and processors."""
    p1 = PipelineItem(config=ProcessorConfig(name='Proc1'))
    p2 = PipelineItem(config=ProcessorConfig(name='Proc2'))

    group_config = GroupConfig(name='GroupA', processors=['Proc3'])
    group = PipelineItem(config=group_config)
    p3 = PipelineItem(config=ProcessorConfig(name='Proc3'), parent=group)
    group.children = [p3]

    pipeline = ProcessorPipeline(items=[p1, group, p2])
    return pipeline


def test_model_initialization() -> None:
    model = ProcessorPipelineModel()
    assert model.rowCount() == 0
    assert model.columnCount() == 1


def test_model_set_pipeline(sample_pipeline: ProcessorPipeline) -> None:
    model = ProcessorPipelineModel()
    model.set_pipeline(sample_pipeline)
    assert model.rowCount() == 3
    assert model.pipeline() is sample_pipeline


def test_model_index(sample_pipeline: ProcessorPipeline) -> None:
    model = ProcessorPipelineModel(sample_pipeline)

    # Valid top-level index
    idx = model.index(0, 0)
    assert idx.isValid()
    assert idx.internalPointer().name() == 'Proc1'

    # Invalid index (out of range)
    assert not model.index(3, 0).isValid()
    assert not model.index(0, 1).isValid()

    # Child index
    parent_idx = model.index(1, 0)  # GroupA
    child_idx = model.index(0, 0, parent_idx)
    assert child_idx.isValid()
    assert child_idx.internalPointer().name() == 'Proc3'


def test_model_parent(sample_pipeline: ProcessorPipeline) -> None:
    model = ProcessorPipelineModel(sample_pipeline)

    group_idx = model.index(1, 0)
    child_idx = model.index(0, 0, group_idx)

    # Parent of child is group
    parent_idx = model.parent(child_idx)
    assert parent_idx.isValid()
    assert parent_idx.internalPointer().name() == 'GroupA'
    assert parent_idx.row() == 1

    # Parent of group is root
    assert not model.parent(group_idx).isValid()

    # Parent of invalid index
    assert not model.parent(QModelIndex()).isValid()


def test_model_data(sample_pipeline: ProcessorPipeline) -> None:
    model = ProcessorPipelineModel(sample_pipeline)
    idx = model.index(0, 0)

    assert model.data(idx, Qt.ItemDataRole.DisplayRole) == 'Proc1'
    assert model.data(idx, PipelineRoles.NAME) == 'Proc1'
    assert model.data(idx, PipelineRoles.ITEM).name() == 'Proc1'
    assert model.data(idx, PipelineRoles.IS_GROUP) is False

    group_idx = model.index(1, 0)
    assert model.data(group_idx, PipelineRoles.IS_GROUP) is True

    # Invalid role
    assert model.data(idx, Qt.ItemDataRole.UserRole + 999) is None
    # Invalid index
    assert model.data(QModelIndex()) is None


def test_model_flags(sample_pipeline: ProcessorPipeline) -> None:
    model = ProcessorPipelineModel(sample_pipeline)

    proc_idx = model.index(0, 0)
    group_idx = model.index(1, 0)

    proc_flags = model.flags(proc_idx)
    assert proc_flags & Qt.ItemFlag.ItemIsDragEnabled
    assert not (proc_flags & Qt.ItemFlag.ItemIsDropEnabled)

    group_flags = model.flags(group_idx)
    assert group_flags & Qt.ItemFlag.ItemIsDropEnabled

    # Invalid index
    assert model.flags(QModelIndex()) == Qt.ItemFlag.ItemIsEnabled


def test_root_items(sample_pipeline: ProcessorPipeline) -> None:
    model = ProcessorPipelineModel(sample_pipeline)
    assert model.root_items() == sample_pipeline.items


def test_row_of_item_edge_cases(sample_pipeline: ProcessorPipeline) -> None:
    model = ProcessorPipelineModel(sample_pipeline)

    # Item not in pipeline
    lonely_item = PipelineItem(config=ProcessorConfig(name='Lonely'))
    assert model._row_of_item(lonely_item) is None

    # Child item not in parent's children (should not happen in normal use but for coverage)
    group = sample_pipeline.items[1]
    rogue_child = PipelineItem(config=ProcessorConfig(name='Rogue'), parent=group)
    assert model._row_of_item(rogue_child) is None


def test_model_parent_null_index() -> None:
    model = ProcessorPipelineModel()
    # Calling parent(None) to hit the super().parent() branch
    assert model.parent(None) is None


def test_model_parent_rogue_parent(sample_pipeline: ProcessorPipeline) -> None:
    model = ProcessorPipelineModel(sample_pipeline)
    rogue_parent = PipelineItem(config=ProcessorConfig(name='RogueParent'))
    child = PipelineItem(config=ProcessorConfig(name='Child'), parent=rogue_parent)
    # Index for a child whose parent is not in the pipeline
    idx = model.createIndex(0, 0, child)
    assert not model.parent(idx).isValid()


def test_model_data_item_none(sample_pipeline: ProcessorPipeline) -> None:
    model = ProcessorPipelineModel(sample_pipeline)
    # A valid but corrupted index that returns None from _item_from_index
    idx = model.createIndex(0, 0, None)
    assert model.data(idx, Qt.ItemDataRole.DisplayRole) is None
