from typing import List, Tuple

import pytest
from pytestqt.qtbot import QtBot
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QTreeWidget, QTreeWidgetItem

from superqt import QSearchableTreeWidget


@pytest.fixture
def data() -> dict:
    return {
        "none": None,
        "str": "test",
        "int": 42,
        "list": [2, 3, 5],
        "dict": {
            "float": 0.5,
            "tuple": (22, 99),
            "bool": False,
        },
    }


@pytest.fixture
def widget(qtbot: QtBot, data: dict) -> QSearchableTreeWidget:
    widget = QSearchableTreeWidget.fromData(data)
    qtbot.addWidget(widget)
    return widget


def columns(item: QTreeWidgetItem) -> Tuple[str, str]:
    return item.text(0), item.text(1)


def all_items(tree: QTreeWidget) -> List[QTreeWidgetItem]:
    return tree.findItems("", Qt.MatchContains | Qt.MatchRecursive)


def shown_items(tree: QTreeWidget) -> List[QTreeWidgetItem]:
    items = all_items(tree)
    return [item for item in items if not item.isHidden()]


def test_init(qtbot: QtBot):
    widget = QSearchableTreeWidget()
    qtbot.addWidget(widget)
    assert widget.tree.topLevelItemCount() == 0


def test_from_data(qtbot: QtBot, data: dict):
    widget = QSearchableTreeWidget.fromData(data)
    qtbot.addWidget(widget)
    tree = widget.tree

    assert tree.topLevelItemCount() == 5

    none_item = tree.topLevelItem(0)
    assert columns(none_item) == ("none", "None")
    assert none_item.childCount() == 0

    str_item = tree.topLevelItem(1)
    assert columns(str_item) == ("str", "test")
    assert str_item.childCount() == 0

    int_item = tree.topLevelItem(2)
    assert columns(int_item) == ("int", "42")
    assert int_item.childCount() == 0

    list_item = tree.topLevelItem(3)
    assert columns(list_item) == ("list", "list")
    assert list_item.childCount() == 3
    assert columns(list_item.child(0)) == ("0", "2")
    assert columns(list_item.child(1)) == ("1", "3")
    assert columns(list_item.child(2)) == ("2", "5")

    dict_item = tree.topLevelItem(4)
    assert columns(dict_item) == ("dict", "dict")
    assert dict_item.childCount() == 3
    assert columns(dict_item.child(0)) == ("float", "0.5")
    tuple_item = dict_item.child(1)
    assert columns(tuple_item) == ("tuple", "tuple")
    assert tuple_item.childCount() == 2
    assert columns(tuple_item.child(0)) == ("0", "22")
    assert columns(tuple_item.child(1)) == ("1", "99")
    assert columns(dict_item.child(2)) == ("bool", "False")


def test_set_data(widget: QSearchableTreeWidget):
    tree = widget.tree
    assert tree.topLevelItemCount() != 1

    widget.setData({"test": "reset"})

    assert tree.topLevelItemCount() == 1
    assert columns(tree.topLevelItem(0)) == ("test", "reset")


def test_search_no_match(widget: QSearchableTreeWidget):
    widget.filter.setText("no match here")
    items = shown_items(widget.tree)
    assert len(items) == 0


def test_search_all_match(widget: QSearchableTreeWidget):
    widget.filter.setText("")
    tree = widget.tree
    assert all_items(tree) == shown_items(tree)


def test_search_match_one_key(widget: QSearchableTreeWidget):
    widget.filter.setText("int")
    items = shown_items(widget.tree)
    assert len(items) == 1
    assert columns(items[0]) == ("int", "42")


def test_search_match_one_value(widget: QSearchableTreeWidget):
    widget.filter.setText("test")
    items = shown_items(widget.tree)
    assert len(items) == 1
    assert columns(items[0]) == ("str", "test")


def test_search_match_many_keys(widget: QSearchableTreeWidget):
    widget.filter.setText("n")
    items = shown_items(widget.tree)
    assert len(items) == 2
    assert columns(items[0]) == ("none", "None")
    assert columns(items[1]) == ("int", "42")


def test_search_match_one_show_unmatched_descendants(widget: QSearchableTreeWidget):
    widget.filter.setText("list")
    items = shown_items(widget.tree)
    assert len(items) == 4
    assert columns(items[0]) == ("list", "list")
    assert columns(items[1]) == ("0", "2")
    assert columns(items[2]) == ("1", "3")
    assert columns(items[3]) == ("2", "5")


def test_search_match_one_show_unmatched_ancestors(widget: QSearchableTreeWidget):
    widget.filter.setText("tuple")
    items = shown_items(widget.tree)
    assert len(items) == 4
    assert columns(items[0]) == ("dict", "dict")
    assert columns(items[1]) == ("tuple", "tuple")
    assert columns(items[2]) == ("0", "22")
    assert columns(items[3]) == ("1", "99")
