# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/

"""
Tests of :mod:`rhodecode.lib.diffs` around the context of a specific line.
"""

import textwrap

import pytest

from rhodecode.lib import diffs
from rhodecode.lib.str_utils import safe_bytes
from rhodecode.lib.vcs.backends.git.diff import GitDiff


def test_context_of_new_and_old_line_number_raises(diff_processor):
    with pytest.raises(ValueError):
        diff_processor.get_context_of_line(path="file.txt", diff_line=diffs.DiffLineNumber(old=7, new=7))


def test_context_of_an_old_line_number(diff_processor):
    context = diff_processor.get_context_of_line(path="file.txt", diff_line=diffs.DiffLineNumber(old=7, new=None))
    expected_context = [
        ("unmod", b"line04\n"),
        ("unmod", b"line05\n"),
        ("unmod", b"line06\n"),
        ("unmod", b"line07\n"),
        ("add", b"line07a Add after line07\n"),
        ("unmod", b"line08\n"),
        ("unmod", b"line09\n"),
    ]
    assert context == expected_context


def test_context_of_a_new_line_number(diff_processor):
    context = diff_processor.get_context_of_line(path="file.txt", diff_line=diffs.DiffLineNumber(old=None, new=8))
    expected_context = [
        ("unmod", b"line05\n"),
        ("unmod", b"line06\n"),
        ("unmod", b"line07\n"),
        ("add", b"line07a Add after line07\n"),
        ("unmod", b"line08\n"),
        ("unmod", b"line09\n"),
        ("unmod", b"line10\n"),
    ]
    assert context == expected_context


def test_context_of_an_invisible_line_beginning_of_hunk(diff_processor):
    # Note: The caller has to pass in a diff which is suitable to satisfy
    # its requirements. This test just ensures that we see a sane behavior.
    context = diff_processor.get_context_of_line(path="file.txt", diff_line=diffs.DiffLineNumber(old=None, new=3))
    expected_context = [
        ("unmod", b"line02\n"),
        ("unmod", b"line03\n"),
        ("unmod", b"line04\n"),
        ("unmod", b"line05\n"),
        ("unmod", b"line06\n"),
    ]
    assert context == expected_context


def test_context_of_an_invisible_line_end_of_hunk(diff_processor):
    # Note: The caller has to pass in a diff which is suitable to satisfy
    # its requirements. This test just ensures that we see a sane behavior.
    context = diff_processor.get_context_of_line(path="file.txt", diff_line=diffs.DiffLineNumber(old=12, new=None))
    expected_context = [
        ("unmod", b"line09\n"),
        ("unmod", b"line10\n"),
        ("unmod", b"line11\n"),
        ("unmod", b"line12\n"),
        ("unmod", b"line13\n"),
    ]
    assert context == expected_context


@pytest.mark.parametrize("diff_fixture", ["change-in-beginning.diff"])
def test_context_of_an_incomplete_hunk_in_the_beginning(diff_processor):
    context = diff_processor.get_context_of_line(path="file.txt", diff_line=diffs.DiffLineNumber(old=None, new=2))
    expected_context = [
        ("unmod", b"line01\n"),
        ("add", b"line01a Add line after line01\n"),
        ("unmod", b"line02\n"),
        ("unmod", b"line03\n"),
        ("unmod", b"line04\n"),
    ]
    assert context == expected_context


@pytest.mark.parametrize("diff_fixture", ["change-in-end.diff"])
def test_context_of_an_incomplete_hunk_in_the_end(diff_processor):
    context = diff_processor.get_context_of_line(path="file.txt", diff_line=diffs.DiffLineNumber(old=None, new=80))
    expected_context = [
        ("unmod", b"line36\n"),
        ("unmod", b"line37\n"),
        ("unmod", b"line38\n"),
        ("add", b"line38a Add line after line38\n"),
        ("unmod", b"line39\n"),
    ]
    assert context == expected_context


@pytest.mark.parametrize(
    "diff_fixture",
    [
        "single-line.diff",
        "single-line-two-files.diff",
    ],
)
def test_appends_newline_for_each_context_line(diff_processor):
    context = diff_processor.get_context_of_line(path="file_b", diff_line=diffs.DiffLineNumber(old=None, new=1))
    assert context == [("add", b"test_content\n")]


def test_context_of_a_missing_line_raises(diff_processor):
    missing_line = 20
    with pytest.raises(diffs.LineNotInDiffException):
        diff_processor.get_context_of_line(path="file.txt", diff_line=diffs.DiffLineNumber(old=None, new=missing_line))


def test_context_of_a_missing_file_raises(diff_processor):
    with pytest.raises(diffs.FileNotInDiffException):
        diff_processor.get_context_of_line(
            path="not_existing_file.txt", diff_line=diffs.DiffLineNumber(old=None, new=8)
        )


def test_find_context_with_full_context(diff_processor):
    context_of_line_7 = [
        ("unmod", b"line05\n"),
        ("unmod", b"line06\n"),
        ("unmod", b"line07\n"),
        ("add", b"line07a Add after line07\n"),
        ("unmod", b"line08\n"),
        ("unmod", b"line09\n"),
        ("unmod", b"line10\n"),
    ]
    found_line = diff_processor.find_context("file.txt", context_of_line_7, offset=3)
    assert found_line == [diffs.DiffLineNumber(old=None, new=8)]


@pytest.mark.parametrize("diff_fixture", ["change-duplicated.diff"])
def test_find_context_multiple_times(diff_processor):
    context = [
        ("unmod", b"line04\n"),
        ("unmod", b"line05\n"),
        ("unmod", b"line06\n"),
        ("add", b"line06a add line\n"),
        ("unmod", b"line07\n"),
        ("unmod", b"line08\n"),
        ("unmod", b"line09\n"),
    ]
    found_line = diff_processor.find_context("file.txt", context, offset=3)
    assert found_line == [
        diffs.DiffLineNumber(old=None, new=7),
        diffs.DiffLineNumber(old=None, new=49),
    ]


@pytest.mark.parametrize("offset", [20, -20, -1, 7])
def test_find_context_offset_param_raises(diff_processor, offset):
    context_of_line_7 = [
        ("unmod", b"line04\n"),
        ("unmod", b"line05\n"),
        ("unmod", b"line06\n"),
        ("unmod", b"line07\n"),
        ("add", b"line07a Add after line07\n"),
        ("unmod", b"line08\n"),
        ("unmod", b"line09\n"),
    ]
    with pytest.raises(ValueError):
        diff_processor.find_context("file.txt", context_of_line_7, offset=offset)


def test_find_context_beginning_of_chunk(diff_processor):
    context_of_first_line = [
        ("unmod", b"line02\n"),
        ("unmod", b"line03\n"),
        ("unmod", b"line04\n"),
        ("unmod", b"line05\n"),
    ]
    found_line = diff_processor.find_context("file.txt", context_of_first_line, offset=0)
    assert found_line == [diffs.DiffLineNumber(old=2, new=2)]


@pytest.mark.parametrize("diff_fixture", ["change-in-beginning.diff"])
def test_find_context_beginning_of_file(diff_processor):
    context_of_first_line = [
        ("add", b"line01a Add line after line01\n"),
        ("unmod", b"line02\n"),
        ("unmod", b"line03\n"),
        ("unmod", b"line04\n"),
        ("unmod", b"line05\n"),
        ("unmod", b"line06\n"),
        ("unmod", b"line07\n"),
    ]
    found_line = diff_processor.find_context("file.txt", context_of_first_line, offset=3)
    assert found_line == [diffs.DiffLineNumber(old=4, new=5)]


def test_find_context_end_of_chunk(diff_processor):
    context_of_last_line = [
        ("unmod", b"line10\n"),
        ("unmod", b"line11\n"),
        ("unmod", b"line12\n"),
        ("unmod", b"line13\n"),
    ]
    found_line = diff_processor.find_context("file.txt", context_of_last_line, offset=3)
    assert found_line == [diffs.DiffLineNumber(old=13, new=14)]


@pytest.fixture()
def diff_processor(request, diff_fixture):
    raw_diff = diffs_store[diff_fixture]
    diff = GitDiff(raw_diff)
    processor = diffs.DiffProcessor(diff, diff_format="newdiff")
    processor.prepare()
    return processor


@pytest.fixture()
def diff_fixture():
    return "default.diff"


diff_default: bytes = safe_bytes(
    textwrap.dedent("""
    diff --git a/file.txt b/file.txt
    index 76e4f2e..6f8738f 100644
    --- a/file.txt
    +++ b/file.txt
    @@ -2,12 +2,13 @@ line01
     line02
     line03
     line04
     line05
     line06
     line07
    +line07a Add after line07
     line08
     line09
     line10
     line11
     line12
     line13
""")
)


diff_beginning: bytes = safe_bytes(
    textwrap.dedent("""
    diff --git a/file.txt b/file.txt
    index 76e4f2e..47d39f4 100644
    --- a/file.txt
    +++ b/file.txt
    @@ -1,7 +1,8 @@
     line01
    +line01a Add line after line01
     line02
     line03
     line04
     line05
     line06
     line07
""")
)


diff_end: bytes = safe_bytes(
    textwrap.dedent("""
    diff --git a/file.txt b/file.txt
    index 76e4f2e..b1304db 100644
    --- a/file.txt
    +++ b/file.txt
    @@ -74,7 +74,8 @@ line32
     line33
     line34
     line35
     line36
     line37
     line38
    +line38a Add line after line38
     line39
""")
)


diff_duplicated_change: bytes = safe_bytes(
    textwrap.dedent("""
    diff --git a/file.txt b/file.txt
    index 76e4f2e..55c2781 100644
    --- a/file.txt
    +++ b/file.txt
    @@ -1,12 +1,13 @@
     line01
     line02
     line03
     line04
     line05
     line06
    +line06a add line
     line07
     line08
     line09
     line10
     line11
     line12
    @@ -42,12 +43,13 @@ line39
     line01
     line02
     line03
     line04
     line05
     line06
    +line06a add line
     line07
     line08
     line09
     line10
     line11
     line12
""")
)


diff_single_line: bytes = safe_bytes(
    textwrap.dedent("""
    diff --git a/file_b b/file_b
    new file mode 100644
    index 00000000..915e94ff
    --- /dev/null
    +++ b/file_b
    @@ -0,0 +1 @@
    +test_content
""")
)


diff_single_line_two_files: bytes = safe_bytes(
    textwrap.dedent("""
    diff --git a/file_b b/file_b
    new file mode 100644
    index 00000000..915e94ff
    --- /dev/null
    +++ b/file_b
    @@ -0,0 +1 @@
    +test_content
    diff --git a/file_c b/file_c
    new file mode 100644
    index 00000000..915e94ff
    --- /dev/null
    +++ b/file_c
    @@ -0,0 +1 @@
    +test_content
""")
)


diffs_store = {
    "default.diff": diff_default,
    "change-in-beginning.diff": diff_beginning,
    "change-in-end.diff": diff_end,
    "change-duplicated.diff": diff_duplicated_change,
    "single-line.diff": diff_single_line,
    "single-line-two-files.diff": diff_single_line_two_files,
}
