"""Tests for Buffer class - core text manipulation."""

import pytest

from e3po.editor import Buffer, UndoType, UndoEntry


class TestBufferBasics:
    """Basic buffer operations."""

    def test_new_buffer_has_one_empty_line(self):
        buf = Buffer()
        assert buf.lines == [""]
        assert not buf.modified

    def test_get_line_valid(self):
        buf = Buffer()
        buf.lines = ["first", "second", "third"]
        assert buf.get_line(0) == "first"
        assert buf.get_line(1) == "second"
        assert buf.get_line(2) == "third"

    def test_get_line_invalid_returns_empty(self):
        buf = Buffer()
        buf.lines = ["only"]
        assert buf.get_line(-1) == ""
        assert buf.get_line(100) == ""

    def test_set_line_existing(self):
        buf = Buffer()
        buf.lines = ["old"]
        buf.set_line(0, "new")
        assert buf.lines[0] == "new"
        assert buf.modified

    def test_set_line_extends_buffer(self):
        buf = Buffer()
        buf.lines = ["first"]
        buf.set_line(3, "fourth")
        assert len(buf.lines) == 4
        assert buf.lines[1] == ""
        assert buf.lines[2] == ""
        assert buf.lines[3] == "fourth"


class TestBufferInsertDelete:
    """Insert and delete operations."""

    def test_insert_char_middle(self):
        buf = Buffer()
        buf.lines = ["hello"]
        buf.insert_char(0, 2, "X")
        assert buf.lines[0] == "heXllo"

    def test_insert_char_start(self):
        buf = Buffer()
        buf.lines = ["hello"]
        buf.insert_char(0, 0, "X")
        assert buf.lines[0] == "Xhello"

    def test_insert_char_end(self):
        buf = Buffer()
        buf.lines = ["hello"]
        buf.insert_char(0, 5, "X")
        assert buf.lines[0] == "helloX"

    def test_insert_char_beyond_line_pads(self):
        buf = Buffer()
        buf.lines = ["hi"]
        buf.insert_char(0, 5, "X")
        assert buf.lines[0] == "hi   X"

    def test_delete_char_middle(self):
        buf = Buffer()
        buf.lines = ["hello"]
        buf.delete_char(0, 2)
        assert buf.lines[0] == "helo"

    def test_delete_char_start(self):
        buf = Buffer()
        buf.lines = ["hello"]
        buf.delete_char(0, 0)
        assert buf.lines[0] == "ello"

    def test_delete_char_beyond_line_noop(self):
        buf = Buffer()
        buf.lines = ["hello"]
        buf.delete_char(0, 10)
        assert buf.lines[0] == "hello"


class TestBufferBackspace:
    """Backspace behavior including line joining."""

    def test_backspace_middle_of_line(self):
        buf = Buffer()
        buf.lines = ["hello"]
        row, col = buf.backspace(0, 3)
        assert buf.lines[0] == "helo"
        assert row == 0
        assert col == 2

    def test_backspace_start_of_line_joins(self):
        buf = Buffer()
        buf.lines = ["first", "second"]
        row, col = buf.backspace(1, 0)
        assert buf.lines == ["firstsecond"]
        assert row == 0
        assert col == 5  # End of "first"

    def test_backspace_start_of_first_line_noop(self):
        buf = Buffer()
        buf.lines = ["hello"]
        row, col = buf.backspace(0, 0)
        assert buf.lines == ["hello"]
        assert row == 0
        assert col == 0


class TestBufferSplitLine:
    """Line splitting (Enter key)."""

    def test_split_line_middle(self):
        buf = Buffer()
        buf.lines = ["hello world"]
        buf.split_line(0, 5)
        assert buf.lines == ["hello", " world"]

    def test_split_line_start(self):
        buf = Buffer()
        buf.lines = ["hello"]
        buf.split_line(0, 0)
        assert buf.lines == ["", "hello"]

    def test_split_line_end(self):
        buf = Buffer()
        buf.lines = ["hello"]
        buf.split_line(0, 5)
        assert buf.lines == ["hello", ""]


class TestBufferLineOperations:
    """Line insert and delete operations."""

    def test_delete_lines_single(self):
        buf = Buffer()
        buf.lines = ["a", "b", "c"]
        deleted = buf.delete_lines(1, 1)
        assert deleted == ["b"]
        assert buf.lines == ["a", "c"]

    def test_delete_lines_range(self):
        buf = Buffer()
        buf.lines = ["a", "b", "c", "d"]
        deleted = buf.delete_lines(1, 2)
        assert deleted == ["b", "c"]
        assert buf.lines == ["a", "d"]

    def test_delete_all_lines_leaves_one_empty(self):
        buf = Buffer()
        buf.lines = ["a", "b"]
        buf.delete_lines(0, 1)
        assert buf.lines == [""]

    def test_insert_lines(self):
        buf = Buffer()
        buf.lines = ["a", "d"]
        buf.insert_lines(1, ["b", "c"])
        assert buf.lines == ["a", "b", "c", "d"]


class TestUndo:
    """Undo stack operations."""

    def test_push_undo_saves_line(self):
        buf = Buffer()
        buf.lines = ["original"]
        buf.push_undo(UndoType.LINE_CHANGE, 0, 0, 0, 0)
        assert len(buf.undo_stack) == 1
        assert buf.undo_stack[0].saved_lines == ["original"]

    def test_push_undo_clears_redo(self):
        buf = Buffer()
        buf.lines = ["test"]
        buf.redo_stack.append(UndoEntry(UndoType.LINE_CHANGE, 0, 0, ["x"], 1))
        buf.push_undo(UndoType.LINE_CHANGE, 0, 0, 0, 0)
        assert len(buf.redo_stack) == 0

    def test_pop_undo_returns_entry(self):
        buf = Buffer()
        buf.lines = ["test"]
        buf.push_undo(UndoType.LINE_CHANGE, 0, 5, 0, 0)
        entry = buf.pop_undo()
        assert entry is not None
        assert entry.undo_type == UndoType.LINE_CHANGE
        assert entry.line_num == 0
        assert entry.col_pos == 5

    def test_pop_undo_empty_returns_none(self):
        buf = Buffer()
        assert buf.pop_undo() is None

    def test_undo_stack_limit(self):
        buf = Buffer()
        buf.max_undo = 5
        buf.lines = ["test"]
        for i in range(10):
            buf.push_undo(UndoType.LINE_CHANGE, 0, i, 0, 0)
        assert len(buf.undo_stack) == 5
        # Oldest entries should be removed
        assert buf.undo_stack[0].col_pos == 5

    def test_clear_undo_clears_both_stacks(self):
        buf = Buffer()
        buf.lines = ["test"]
        buf.push_undo(UndoType.LINE_CHANGE, 0, 0, 0, 0)
        buf.redo_stack.append(UndoEntry(UndoType.LINE_CHANGE, 0, 0, ["x"], 1))
        buf.clear_undo()
        assert len(buf.undo_stack) == 0
        assert len(buf.redo_stack) == 0


class TestUndoTypes:
    """Test different undo operation types."""

    def test_line_change_saves_single_line(self):
        buf = Buffer()
        buf.lines = ["line0", "line1", "line2"]
        buf.push_undo(UndoType.LINE_CHANGE, 1, 0, 1, 1)
        assert buf.undo_stack[0].saved_lines == ["line1"]
        assert buf.undo_stack[0].line_count == 1

    def test_block_change_saves_multiple_lines(self):
        buf = Buffer()
        buf.lines = ["line0", "line1", "line2", "line3"]
        buf.push_undo(UndoType.BLOCK_CHANGE, 1, 0, 1, 2)
        assert buf.undo_stack[0].saved_lines == ["line1", "line2"]
        assert buf.undo_stack[0].line_count == 2

    def test_line_split_saves_original(self):
        buf = Buffer()
        buf.lines = ["hello world"]
        buf.push_undo(UndoType.LINE_SPLIT, 0, 5, 0, 0)
        assert buf.undo_stack[0].saved_lines == ["hello world"]

    def test_line_join_saves_both_lines(self):
        buf = Buffer()
        buf.lines = ["first", "second"]
        buf.push_undo(UndoType.LINE_JOIN, 0, 5, 0, 1)
        assert buf.undo_stack[0].saved_lines == ["first", "second"]


class TestModifiedLines:
    """Test modified line tracking for gutter markers."""

    def test_modified_lines_starts_empty(self):
        buf = Buffer()
        assert buf.modified_lines == set()

    def test_set_line_tracks_modification(self):
        buf = Buffer()
        buf.lines = ["line 0", "line 1", "line 2"]
        buf.set_line(1, "changed")
        assert 1 in buf.modified_lines

    def test_multiple_lines_tracked(self):
        buf = Buffer()
        buf.lines = ["a", "b", "c"]
        buf.set_line(0, "A")
        buf.set_line(2, "C")
        assert buf.modified_lines == {0, 2}

    def test_save_clears_modified_lines(self):
        import tempfile
        import os
        buf = Buffer()
        buf.lines = ["line 0", "line 1"]
        buf.set_line(0, "changed")
        buf.set_line(1, "also changed")
        assert buf.modified_lines == {0, 1}

        with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
            fname = f.name
        try:
            buf.save(fname)
            assert buf.modified_lines == set()
        finally:
            os.unlink(fname)
