import pytest

from PySide6.QtGui import QTextCursor
from PySide6.QtWidgets import QTextEdit, QWidget
from bouquin.markdown_editor import MarkdownEditor
from bouquin.theme import ThemeManager, ThemeConfig, Theme
from bouquin.find_bar import FindBar


@pytest.fixture
def editor(app, qtbot):
    themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
    ed = MarkdownEditor(themes)
    qtbot.addWidget(ed)
    ed.show()
    return ed


def test_findbar_basic_navigation(qtbot, editor):
    editor.from_markdown("alpha\nbeta\nalpha\nGamma\n")
    editor.moveCursor(QTextCursor.Start)

    fb = FindBar(editor, parent=editor)
    qtbot.addWidget(fb)
    fb.show_bar()
    fb.edit.setText("alpha")
    fb.find_next()
    pos1 = editor.textCursor().position()
    fb.find_next()
    pos2 = editor.textCursor().position()
    assert pos2 > pos1

    fb.find_prev()
    pos3 = editor.textCursor().position()
    assert pos3 <= pos2

    fb.case.setChecked(True)
    fb.refresh()
    fb.hide_bar()


def test_show_bar_seeds_selection(qtbot, editor):

    editor.from_markdown("alpha beta")
    c = editor.textCursor()
    c.movePosition(QTextCursor.Start)
    c.movePosition(QTextCursor.NextWord, QTextCursor.KeepAnchor)
    editor.setTextCursor(c)

    fb = FindBar(editor, parent=editor)
    qtbot.addWidget(fb)
    fb.show_bar()
    assert fb.edit.text().lower() == "alpha"
    fb.hide_bar()


def test_show_bar_no_editor(qtbot, app):
    fb = FindBar(lambda: None)
    qtbot.addWidget(fb)
    fb.show_bar()  # should early return without crashing and not become visible
    assert not fb.isVisible()


def test_show_bar_ignores_multi_paragraph_selection(qtbot, editor):
    editor.from_markdown("alpha\n\nbeta")
    c = editor.textCursor()
    c.movePosition(QTextCursor.Start)
    # Select across the paragraph separator U+2029 equivalent – select more than one block
    c.movePosition(QTextCursor.End, QTextCursor.KeepAnchor)
    editor.setTextCursor(c)
    fb = FindBar(lambda: editor, parent=editor)
    qtbot.addWidget(fb)
    fb.show_bar()
    assert fb.edit.text() == ""  # should not seed with multi-paragraph
    fb.hide_bar()


def test_find_wraps_and_bumps_caret(qtbot, editor):
    editor.from_markdown("alpha alpha alpha")
    fb = FindBar(lambda: editor, parent=editor)
    qtbot.addWidget(fb)
    fb.edit.setText("alpha")

    # Select the first occurrence so caret bumping path triggers
    c = editor.textCursor()
    c.movePosition(QTextCursor.Start)
    c.movePosition(QTextCursor.NextWord, QTextCursor.KeepAnchor)
    editor.setTextCursor(c)

    fb.find_next()  # should bump to after current selection then find next
    sel = editor.textCursor().selectedText()
    assert sel.lower() == "alpha"

    # Force wrap to start by moving cursor to end then searching next
    c = editor.textCursor()
    c.movePosition(QTextCursor.End)
    editor.setTextCursor(c)
    fb.find_next()  # triggers wrap-to-start path
    assert editor.textCursor().hasSelection()


def test_update_highlight_clear_when_empty(qtbot, editor):
    editor.from_markdown("find me find me")
    fb = FindBar(lambda: editor, parent=editor)
    qtbot.addWidget(fb)
    fb.edit.setText("find")
    fb._update_highlight()
    assert editor.extraSelections()  # some highlights present

    fb.edit.setText("")
    fb._update_highlight()  # should clear
    assert not editor.extraSelections()


def test_maybe_hide_and_wrap_prev(qtbot, editor):
    editor.setPlainText("a a a")
    fb = FindBar(editor=editor, shortcut_parent=editor)
    qtbot.addWidget(editor)
    qtbot.addWidget(fb)
    editor.show()
    fb.show()

    fb.edit.setText("a")
    fb._update_highlight()

    assert fb.isVisible()
    fb._maybe_hide()
    assert not fb.isVisible()

    fb.show_bar()
    c = editor.textCursor()
    c.movePosition(QTextCursor.Start)
    editor.setTextCursor(c)
    fb.find_prev()


def _make_fb(editor, qtbot):
    """Create a FindBar with a live parent kept until teardown."""
    parent = QWidget()
    qtbot.addWidget(parent)
    fb = FindBar(editor=editor, parent=parent)
    qtbot.addWidget(fb)
    parent.show()
    fb.show()
    return fb, parent


def test_find_next_early_returns_no_editor(qtbot):
    # No editor: should early return and not crash
    fb, _parent = _make_fb(editor=None, qtbot=qtbot)
    fb.find_next()


def test_find_next_early_returns_empty_text(qtbot):
    ed = QTextEdit()
    fb, _parent = _make_fb(editor=ed, qtbot=qtbot)
    fb.edit.setText("")  # empty -> early return
    fb.find_next()


def test_find_prev_early_returns_empty_text(qtbot):
    ed = QTextEdit()
    fb, _parent = _make_fb(editor=ed, qtbot=qtbot)
    fb.edit.setText("")  # empty -> early return
    fb.find_prev()


def test_update_highlight_early_returns_no_editor(qtbot):
    fb, _parent = _make_fb(editor=None, qtbot=qtbot)
    fb.edit.setText("abc")
    fb._update_highlight()  # should return without error
