"""Tests for the V4A patch format parser."""

from types import SimpleNamespace

from tools.patch_parser import (
    OperationType,
    apply_v4a_operations,
    parse_v4a_patch,
)


class TestParseUpdateFile:
    def test_basic_update(self):
        patch = """\
*** Begin Patch
*** Update File: src/main.py
@@ def greet @@
 def greet():
-    print("hello")
+    print("hi")
*** End Patch"""
        ops, err = parse_v4a_patch(patch)
        assert err is None
        assert len(ops) == 1

        op = ops[0]
        assert op.operation == OperationType.UPDATE
        assert op.file_path == "src/main.py"
        assert len(op.hunks) == 1

        hunk = op.hunks[0]
        assert hunk.context_hint == "def greet"
        prefixes = [l.prefix for l in hunk.lines]
        assert " " in prefixes
        assert "-" in prefixes
        assert "+" in prefixes

    def test_multiple_hunks(self):
        patch = """\
*** Begin Patch
*** Update File: f.py
@@ first @@
 a
-b
+c
@@ second @@
 x
-y
+z
*** End Patch"""
        ops, err = parse_v4a_patch(patch)
        assert err is None
        assert len(ops) == 1
        assert len(ops[0].hunks) == 2
        assert ops[0].hunks[0].context_hint == "first"
        assert ops[0].hunks[1].context_hint == "second"


class TestParseAddFile:
    def test_add_file(self):
        patch = """\
*** Begin Patch
*** Add File: new/module.py
+import os
+
+print("hello")
*** End Patch"""
        ops, err = parse_v4a_patch(patch)
        assert err is None
        assert len(ops) == 1

        op = ops[0]
        assert op.operation == OperationType.ADD
        assert op.file_path == "new/module.py"
        assert len(op.hunks) == 1

        contents = [l.content for l in op.hunks[0].lines if l.prefix == "+"]
        assert contents[0] == "import os"
        assert contents[2] == 'print("hello")'


class TestParseDeleteFile:
    def test_delete_file(self):
        patch = """\
*** Begin Patch
*** Delete File: old/stuff.py
*** End Patch"""
        ops, err = parse_v4a_patch(patch)
        assert err is None
        assert len(ops) == 1
        assert ops[0].operation == OperationType.DELETE
        assert ops[0].file_path == "old/stuff.py"


class TestParseMoveFile:
    def test_move_file(self):
        patch = """\
*** Begin Patch
*** Move File: old/path.py -> new/path.py
*** End Patch"""
        ops, err = parse_v4a_patch(patch)
        assert err is None
        assert len(ops) == 1
        assert ops[0].operation == OperationType.MOVE
        assert ops[0].file_path == "old/path.py"
        assert ops[0].new_path == "new/path.py"


class TestParseInvalidPatch:
    def test_empty_patch_returns_empty_ops(self):
        ops, err = parse_v4a_patch("")
        assert err is None
        assert ops == []

    def test_no_begin_marker_still_parses(self):
        patch = """\
*** Update File: f.py
 line1
-old
+new
*** End Patch"""
        ops, err = parse_v4a_patch(patch)
        assert err is None
        assert len(ops) == 1

    def test_multiple_operations(self):
        patch = """\
*** Begin Patch
*** Add File: a.py
+content_a
*** Delete File: b.py
*** Update File: c.py
 keep
-remove
+add
*** End Patch"""
        ops, err = parse_v4a_patch(patch)
        assert err is None
        assert len(ops) == 3
        assert ops[0].operation == OperationType.ADD
        assert ops[1].operation == OperationType.DELETE
        assert ops[2].operation == OperationType.UPDATE


class TestApplyUpdate:
    def test_preserves_non_prefix_pipe_characters_in_unmodified_lines(self):
        patch = """\
*** Begin Patch
*** Update File: sample.py
@@ result @@
     result = 1
-    return result
+    return result + 1
*** End Patch"""
        operations, err = parse_v4a_patch(patch)
        assert err is None

        class FakeFileOps:
            def __init__(self):
                self.written = None

            def read_file(self, path, offset=1, limit=500):
                return SimpleNamespace(
                    content=(
                        'def run():\n'
                        '    cmd = "echo a | sed s/a/b/"\n'
                        '    result = 1\n'
                        '    return result'
                    ),
                    error=None,
                )

            def write_file(self, path, content):
                self.written = content
                return SimpleNamespace(error=None)

        file_ops = FakeFileOps()

        result = apply_v4a_operations(operations, file_ops)

        assert result.success is True
        assert file_ops.written == (
            'def run():\n'
            '    cmd = "echo a | sed s/a/b/"\n'
            '    result = 1\n'
            '    return result + 1'
        )


class TestAdditionOnlyHunks:
    """Regression tests for #3081 — addition-only hunks were silently dropped."""

    def test_addition_only_hunk_with_context_hint(self):
        """A hunk with only + lines should insert at the context hint location."""
        patch = """\
*** Begin Patch
*** Update File: src/app.py
@@ def main @@
+def helper():
+    return 42
*** End Patch"""
        ops, err = parse_v4a_patch(patch)
        assert err is None
        assert len(ops) == 1
        assert len(ops[0].hunks) == 1

        hunk = ops[0].hunks[0]
        # All lines should be additions
        assert all(l.prefix == '+' for l in hunk.lines)

        # Apply to a file that contains the context hint
        class FakeFileOps:
            written = None
            def read_file(self, path, **kw):
                return SimpleNamespace(
                    content="def main():\n    pass\n",
                    error=None,
                )
            def write_file(self, path, content):
                self.written = content
                return SimpleNamespace(error=None)

        file_ops = FakeFileOps()
        result = apply_v4a_operations(ops, file_ops)
        assert result.success is True
        assert "def helper():" in file_ops.written
        assert "return 42" in file_ops.written

    def test_addition_only_hunk_without_context_hint(self):
        """A hunk with only + lines and no context hint appends at end of file."""
        patch = """\
*** Begin Patch
*** Update File: src/app.py
+def new_func():
+    return True
*** End Patch"""
        ops, err = parse_v4a_patch(patch)
        assert err is None

        class FakeFileOps:
            written = None
            def read_file(self, path, **kw):
                return SimpleNamespace(
                    content="existing = True\n",
                    error=None,
                )
            def write_file(self, path, content):
                self.written = content
                return SimpleNamespace(error=None)

        file_ops = FakeFileOps()
        result = apply_v4a_operations(ops, file_ops)
        assert result.success is True
        assert file_ops.written.endswith("def new_func():\n    return True\n")
        assert "existing = True" in file_ops.written
