# SPDX-FileCopyrightText: Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Tests for CompletionModel."""

from unittest import mock
import hypothesis
from hypothesis import strategies

import pytest
from qutebrowser.qt.core import QModelIndex

from qutebrowser.completion.models import completionmodel, listcategory
from qutebrowser.utils import qtutils
from qutebrowser.api import cmdutils


@hypothesis.given(strategies.lists(
    min_size=0, max_size=3,
    elements=strategies.integers(min_value=0, max_value=2**31)))
def test_first_last_item(counts):
    """Test that first() and last() index to the first and last items."""
    model = completionmodel.CompletionModel()
    for c in counts:
        cat = mock.Mock(spec=['layoutChanged', 'layoutAboutToBeChanged'])
        cat.rowCount = mock.Mock(return_value=c, spec=[])
        model.add_category(cat)
    data = [i for i, row_count in enumerate(counts) if row_count > 0]
    if not data:
        # with no items, first and last should be an invalid index
        assert not model.first_item().isValid()
        assert not model.last_item().isValid()
    else:
        first = data[0]
        last = data[-1]
        # first item of the first data category
        assert model.first_item().row() == 0
        assert model.first_item().parent().row() == first
        # last item of the last data category
        assert model.last_item().row() == counts[last] - 1
        assert model.last_item().parent().row() == last


@hypothesis.given(strategies.lists(elements=strategies.integers(),
                                   min_size=0, max_size=3))
def test_count(counts):
    model = completionmodel.CompletionModel()
    for c in counts:
        cat = mock.Mock(spec=['rowCount', 'layoutChanged',
                              'layoutAboutToBeChanged'])
        cat.rowCount = mock.Mock(return_value=c, spec=[])
        model.add_category(cat)
    assert model.count() == sum(counts)


@hypothesis.given(pat=strategies.text())
def test_set_pattern(pat, qtbot):
    """Validate the filtering and sorting results of set_pattern."""
    model = completionmodel.CompletionModel()
    cats = [mock.Mock(spec=['set_pattern']) for _ in range(3)]
    for c in cats:
        c.set_pattern = mock.Mock(spec=[])
        model.add_category(c)
    with qtbot.wait_signals([model.layoutAboutToBeChanged, model.layoutChanged],
                           order='strict'):
        model.set_pattern(pat)
    for c in cats:
        c.set_pattern.assert_called_with(pat)


def test_delete_cur_item():
    func = mock.Mock(spec=[])
    model = completionmodel.CompletionModel()
    cat = listcategory.ListCategory('', [('foo', 'bar')], delete_func=func)
    model.add_category(cat)
    parent = model.index(0, 0)
    model.delete_cur_item(model.index(0, 0, parent))
    func.assert_called_once_with(['foo', 'bar'])


def test_delete_cur_item_no_func():
    callback = mock.Mock(spec=[])
    model = completionmodel.CompletionModel()
    cat = listcategory.ListCategory('', [('foo', 'bar')], delete_func=None)
    model.rowsAboutToBeRemoved.connect(callback)
    model.rowsRemoved.connect(callback)
    model.add_category(cat)
    parent = model.index(0, 0)
    with pytest.raises(cmdutils.CommandError):
        model.delete_cur_item(model.index(0, 0, parent))
    callback.assert_not_called()


def test_delete_cur_item_no_cat():
    """Test completion_item_del with no selected category."""
    callback = mock.Mock(spec=[])
    model = completionmodel.CompletionModel()
    model.rowsAboutToBeRemoved.connect(callback)
    model.rowsRemoved.connect(callback)
    with pytest.raises(qtutils.QtValueError):
        model.delete_cur_item(QModelIndex())
    callback.assert_not_called()
