"""Tests for undo/redo functionality."""

import pytest

from e3po.editor import Buffer, UndoType, UndoEntry, Editor, MarkMode, Mark


class MockScreen:
    """Mock curses screen for testing."""
    def __init__(self):
        self.height = 24
        self.width = 80

    def getmaxyx(self):
        return (self.height, self.width)

    def clear(self):
        pass

    def refresh(self):
        pass

    def addstr(self, *args):
        pass

    def move(self, row, col):
        pass

    def getch(self):
        return -1


def make_editor(lines=None):
    """Create an editor with mock screen for testing."""
    editor = Editor()
    editor.screen = MockScreen()
    editor.height = 24
    editor.width = 80
    editor.edit_height = 20
    if lines:
        editor.buffer.lines = lines.copy()
    return editor


class TestUndoLineChange:
    """Test undo of single line changes."""

    def test_undo_typing(self):
        editor = make_editor(["hello"])
        # Simulate typing: save state, modify
        editor.buffer.push_undo(UndoType.LINE_CHANGE, 0, 5, 0, 0)
        editor.buffer.lines[0] = "hello world"

        assert editor.buffer.lines[0] == "hello world"
        editor.action_undo()
        assert editor.buffer.lines[0] == "hello"

    def test_undo_delete(self):
        editor = make_editor(["hello"])
        editor.buffer.push_undo(UndoType.LINE_CHANGE, 0, 2, 0, 0)
        editor.buffer.lines[0] = "helo"

        editor.action_undo()
        assert editor.buffer.lines[0] == "hello"

    def test_undo_restores_cursor(self):
        editor = make_editor(["hello"])
        editor.cursor_row = 0
        editor.cursor_col = 5
        editor.buffer.push_undo(UndoType.LINE_CHANGE, 0, 3, 0, 0)
        editor.buffer.lines[0] = "modified"
        editor.cursor_col = 8

        editor.action_undo()
        assert editor.cursor_row == 0
        assert editor.cursor_col == 3  # Restored to saved position


class TestUndoLineSplit:
    """Test undo of line splits (Enter key)."""

    def test_undo_line_split(self):
        editor = make_editor(["hello world"])
        editor.buffer.push_undo(UndoType.LINE_SPLIT, 0, 5, 0, 0)
        # Simulate split
        editor.buffer.lines = ["hello", " world"]

        editor.action_undo()
        assert editor.buffer.lines == ["hello world"]

    def test_undo_split_at_start(self):
        editor = make_editor(["hello"])
        editor.buffer.push_undo(UndoType.LINE_SPLIT, 0, 0, 0, 0)
        editor.buffer.lines = ["", "hello"]

        editor.action_undo()
        assert editor.buffer.lines == ["hello"]


class TestUndoLineJoin:
    """Test undo of line joins (Backspace at start)."""

    def test_undo_line_join(self):
        editor = make_editor(["first", "second"])
        editor.buffer.push_undo(UndoType.LINE_JOIN, 0, 5, 0, 1)
        # Simulate join
        editor.buffer.lines = ["firstsecond"]

        editor.action_undo()
        assert editor.buffer.lines == ["first", "second"]


class TestUndoLineInsert:
    """Test undo of line insertions."""

    def test_undo_line_insert(self):
        editor = make_editor(["a", "b"])
        # Simulate inserting a line
        editor.buffer.push_undo(UndoType.LINE_INSERT, 1, 0, 1, 1)
        editor.buffer.lines = ["a", "new", "b"]

        editor.action_undo()
        assert editor.buffer.lines == ["a", "b"]

    def test_undo_multiple_line_insert(self):
        editor = make_editor(["a", "d"])
        # Simulate inserting two lines
        entry = UndoEntry(
            undo_type=UndoType.LINE_INSERT,
            line_num=1,
            col_pos=0,
            saved_lines=[],
            line_count=2
        )
        editor.buffer.undo_stack.append(entry)
        editor.buffer.lines = ["a", "b", "c", "d"]

        editor.action_undo()
        assert editor.buffer.lines == ["a", "d"]


class TestUndoLineDelete:
    """Test undo of line deletions."""

    def test_undo_line_delete(self):
        editor = make_editor(["a", "c"])
        # Simulate having deleted line "b"
        entry = UndoEntry(
            undo_type=UndoType.LINE_DELETE,
            line_num=1,
            col_pos=0,
            saved_lines=["b"],
            line_count=1
        )
        editor.buffer.undo_stack.append(entry)

        editor.action_undo()
        assert editor.buffer.lines == ["a", "b", "c"]

    def test_undo_multiple_line_delete(self):
        editor = make_editor(["a", "d"])
        entry = UndoEntry(
            undo_type=UndoType.LINE_DELETE,
            line_num=1,
            col_pos=0,
            saved_lines=["b", "c"],
            line_count=2
        )
        editor.buffer.undo_stack.append(entry)

        editor.action_undo()
        assert editor.buffer.lines == ["a", "b", "c", "d"]


class TestUndoBlockChange:
    """Test undo of block changes (multiple lines modified)."""

    def test_undo_block_change(self):
        editor = make_editor(["AAA", "BBB", "CCC"])
        editor.buffer.push_undo(UndoType.BLOCK_CHANGE, 0, 0, 0, 1)
        # Modify the lines
        editor.buffer.lines = ["XXX", "YYY", "CCC"]

        editor.action_undo()
        assert editor.buffer.lines == ["AAA", "BBB", "CCC"]


class TestRedo:
    """Test redo functionality."""

    def test_redo_after_undo(self):
        editor = make_editor(["original"])
        editor.buffer.push_undo(UndoType.LINE_CHANGE, 0, 0, 0, 0)
        editor.buffer.lines[0] = "modified"

        editor.action_undo()
        assert editor.buffer.lines[0] == "original"

        editor.action_redo()
        assert editor.buffer.lines[0] == "modified"

    def test_redo_nothing_to_redo(self):
        editor = make_editor(["test"])
        editor.action_redo()
        assert "Nothing to redo" in editor.message

    def test_redo_cleared_on_new_edit(self):
        editor = make_editor(["original"])
        editor.buffer.push_undo(UndoType.LINE_CHANGE, 0, 0, 0, 0)
        editor.buffer.lines[0] = "modified"
        editor.action_undo()

        assert len(editor.buffer.redo_stack) > 0

        # New edit should clear redo stack
        editor.buffer.push_undo(UndoType.LINE_CHANGE, 0, 0, 0, 0)
        assert len(editor.buffer.redo_stack) == 0

    def test_multiple_undo_redo(self):
        editor = make_editor(["v1"])

        editor.buffer.push_undo(UndoType.LINE_CHANGE, 0, 0, 0, 0)
        editor.buffer.lines[0] = "v2"

        editor.buffer.push_undo(UndoType.LINE_CHANGE, 0, 0, 0, 0)
        editor.buffer.lines[0] = "v3"

        assert editor.buffer.lines[0] == "v3"

        editor.action_undo()
        assert editor.buffer.lines[0] == "v2"

        editor.action_undo()
        assert editor.buffer.lines[0] == "v1"

        editor.action_redo()
        assert editor.buffer.lines[0] == "v2"

        editor.action_redo()
        assert editor.buffer.lines[0] == "v3"


class TestUndoWithMarks:
    """Test undo of mark operations."""

    def test_undo_mark_delete(self):
        editor = make_editor(["a", "b", "c"])
        editor.cursor_row = 1
        editor.action_mark_line()
        editor.action_delete()

        assert editor.buffer.lines == ["a", "c"]

        editor.action_undo()
        assert editor.buffer.lines == ["a", "b", "c"]

    def test_undo_shift_left(self):
        editor = make_editor(["    indented"])
        editor.cursor_row = 0
        editor.action_mark_line()
        editor.action_shift_mark_left()

        assert editor.buffer.lines[0] == "   indented"

        editor.action_undo()
        assert editor.buffer.lines[0] == "    indented"

    def test_undo_shift_right(self):
        editor = make_editor(["text"])
        editor.cursor_row = 0
        editor.action_mark_line()
        editor.action_shift_mark_right()

        assert editor.buffer.lines[0] == " text"

        editor.action_undo()
        assert editor.buffer.lines[0] == "text"
