# SPDX-FileCopyrightText: 2016-2024 Michael Bryan <michaelfbryan@gmail.com> - Ken Mijime <kenaco666@gmail.com>
# SPDX-FileCopyrightText: 2024-present Fabien Hermitte
#
# SPDX-License-Identifier: MIT

from __future__ import annotations

import logging
import os
import subprocess

from datetime import date

import pytest

from click.testing import CliRunner
from foxy_project.__main__ import main


# pylint: disable=redefined-outer-name
@pytest.fixture
def commands():
    return []


@pytest.fixture
def path_project():
    return os.getcwd()


@pytest.fixture
def test_repo(tmp_path, commands):
    cwd = os.getcwd()
    os.chdir(str(tmp_path))
    init_commands = [
        "git init -q",
        "git config user.name 'John Doe'",
        "git config user.email john.doe@email",
        "git config commit.gpgsign false",  # will prevent gpg pass failures
    ]
    for command in init_commands + commands:
        # shell argument fixes error for strings. Details in link below:
        # https://stackoverflow.com/questions/9935151/popen-error-errno-2-no-such-file-or-directory
        subprocess.run(command, shell=True, check=False)  # pylint: disable=subprocess-run-check
    yield str(tmp_path)
    os.chdir(cwd)


@pytest.fixture
def runner():
    return CliRunner(mix_stderr=False)


@pytest.fixture
def changelog_name():
    return "CHANGELOG.md"


# pylint: disable=redefined-outer-name
@pytest.fixture
def open_changelog(test_repo, changelog_name):
    file = None

    def _open_changelog():
        nonlocal file
        file = open(changelog_name, encoding="utf-8")  # pylint: disable=consider-using-with
        return file

    yield _open_changelog

    if file:
        file.close()


def test_help(runner):
    result = runner.invoke(main, ["--help"])
    assert result.exit_code == 0, result.stderr
    assert result.output


@pytest.mark.parametrize(
    "commands",
    [["git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git"]],
)
def test_empty_repo(runner, open_changelog):
    """JS test notes:
    * js generates H3 instead of H1
    * js contains explanation what will be content of the file - I don't think this is necessary
    * js contains "Generated by auto-changelog" credentials
    """
    result = runner.invoke(main)
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert changelog == "# Changelog\n"


@pytest.mark.parametrize(
    "commands",
    [["git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git"]],
)
def test_option_repo(test_repo, runner, open_changelog):
    result = runner.invoke(main, ["--path-repo", test_repo])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert changelog == "# Changelog\n"


@pytest.mark.parametrize(
    "commands",
    [["git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git"]],
)
def test_option_title(runner, open_changelog):
    result = runner.invoke(main, ["--title", "Title"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert changelog == "# Title\n"


@pytest.mark.parametrize(
    "commands",
    [["git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git"]],
)
def test_option_description(runner, open_changelog):
    result = runner.invoke(main, ["--description", "My description"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert changelog == "# Changelog\n\nMy description\n"


@pytest.mark.parametrize("changelog_name", ["a.out"])
@pytest.mark.parametrize(
    "commands",
    [["git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git"]],
)
def test_option_output(runner, open_changelog):
    result = runner.invoke(main, ["--output", "a.out"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert changelog == "# Changelog\n"


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1"',
            "git remote add upstream git@github.com:Michael-F-Bryan/auto-changelog.git",
            "git remote add origin git@github.com:KeNaCo/auto-changelog.git",
        ]
    ],
)
def test_option_remote(runner, open_changelog):
    """JS test notes:
    * commit not recognized or included - a bug: https://github.com/cookpete/auto-changelog/issues/174
    """
    result = runner.invoke(main, ["--remote", "upstream", "--unreleased"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n"
        f"* Add file [#1](https://github.com/Michael-F-Bryan/auto-changelog/issues/1)\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1"',
            "git remote add upstream git@github.com:Michael-F-Bryan/auto-changelog.git",
            "git remote add origin git@github.com:KeNaCo/auto-changelog.git",
        ]
    ],
)
def test_option_default_remote(runner, open_changelog):
    result = runner.invoke(main, ["--unreleased"])  # --remote default: origin
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n"
        f"* Add file [#1](https://github.com/KeNaCo/auto-changelog/issues/1)\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1"',
            "git remote add upstream git@github.com:Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_option_default_missing_remote(runner, open_changelog):
    result = runner.invoke(main, ["--unreleased"])  # --remote default: origin
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n"
        f"* Add file #1\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1"',
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_option_latest_version(runner, open_changelog):
    result = runner.invoke(main, ["--latest-version", "1.0.0"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## 1.0.0 ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n"
        f"* Add file [#1](https://github.com/Michael-F-Bryan/auto-changelog/issues/1)\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1"',
        ]
    ],
)
def test_option_unreleased(runner, open_changelog):
    result = runner.invoke(main, ["--unreleased"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n"
        f"* Add file #1\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1"',
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_option_skipping_unreleased(runner, open_changelog):
    result = runner.invoke(main)
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert changelog == "# Changelog\n"
    assert "## Unreleased" not in changelog


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1"',
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_option_issue_url(runner, open_changelog):
    result = runner.invoke(main, ["--issue-url", "issues.custom.com/{id}", "--unreleased"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n"
        f"* Add file [#1](issues.custom.com/1)\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file PRO-1"',
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_option_issue_pattern(runner, open_changelog):
    result = runner.invoke(
        main,
        ["--issue-pattern", r"([a-zA-Z][a-zA-Z][a-zA-Z]-\d+)", "--issue-url", "issues.custom.com/{id}", "--unreleased"],
    )
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n"
        f"* Add file [PRO-1](issues.custom.com/PRO-1)\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file PRO-1"',
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_option_invalid_issue_pattern(runner, open_changelog):
    result = runner.invoke(
        main,
        ["--issue-pattern", r" [a-zA-Z][a-zA-Z][a-zA-Z]-\d+", "--issue-url", "issues.custom.com/{id}", "--unreleased"],
    )
    assert result.exit_code != 0, result.stderr


@pytest.mark.parametrize(
    "commands",
    [["git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git"]],
)
def test_option_stdout(runner, open_changelog):
    result = runner.invoke(main, ["--stdout"])
    assert result.exit_code == 0, result.stderr
    assert result.output == "# Changelog\n\n"


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file"',
            "git tag custom-tag",
            'git commit --allow-empty -q -m "chore: Change"',
            "git tag 1.0.0",
            'git commit --allow-empty -q -m "chore: Change2"'
            "git tag v2.0.0"
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_option_tag_pattern(runner, open_changelog):
    """JS test notes:
    * we are using full-match but JS is using match
    """
    result = runner.invoke(main, ["--tag-pattern", r"\d+.\d+.\d+"])
    assert result.exit_code == 0, result.stderr
    changelog = open_changelog().read()
    assert "1.0.0" in changelog
    assert_content = (
        f"# Changelog\n\n## 1.0.0 ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n"
        f"* Add file\n#### Others\n\n* Change\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file"',
            "git tag v-something",
            'git commit --allow-empty -q -m "chore: Change"',
            "git tag 1.0.0",
            "git tag v2.0.0",
            'git commit --allow-empty -q -m "chore: Change2"',
            "git tag v3.0.0",
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_option_tag_prefix(runner, open_changelog):
    """JS test notes:
    * JS version is a mess, reported https://github.com/cookpete/auto-changelog/issues/193
    """
    result = runner.invoke(main, ["--tag-prefix", "v"])
    assert result.exit_code == 0, result.exc_info
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## v3.0.0 ({date.today().strftime('%Y-%m-%d')})\n\n#### Others\n\n* Change2\n\n"
        f"Full set of changes: [`v2.0.0...v3.0.0`]"
        f"(https://github.com/Michael-F-Bryan/auto-changelog/compare/v2.0.0...v3.0.0)\n\n"
        f"## v2.0.0 ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n* Add file\n"
        f"#### Others\n\n* Change\n"
    )
    assert changelog == assert_content
    assert "1.0.0" not in changelog
    assert "v-something" not in changelog
    assert "v2.0.0" in changelog
    assert "v3.0.0" in changelog


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file"',
            "git tag release-1",
            'git commit --allow-empty -q -m "chore: Change"',
            "git tag 1",
            "git tag release-1.2.3",
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_tag_prefix_and_pattern_combination(runner, open_changelog):
    """Combination of prefix and pattern work in a way that first prefix tags are filtered,
    then tag - (minus) prefix part is checked with the pattern.

    JS test notes:
    * includes also release-1.2.3
    """
    result = runner.invoke(main, ["--tag-prefix", "release-", "--tag-pattern", r"\d"])
    assert result.exit_code == 0, result.stderr
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## release-1 ({date.today().strftime('%Y-%m-%d')})\n\n" f"#### New Features\n\n* Add file\n"
    )
    assert changelog == assert_content
    assert "## 1 " not in changelog
    assert "release-1.2.3" not in changelog


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file PRO-1"',
            'git commit --allow-empty -q -m "fix: Some file fix"',
            "git tag start",
            "git tag 1.0.0",
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_starting_commit(test_repo, runner, open_changelog):
    """JS test notes:
    * not supported by JS
    * JS have --starting-version [tag] which is more specific variant of this attribute
    * maybe test also different references than tag?
    """
    result = runner.invoke(main, ["--starting-commit", "start"])
    assert result.exit_code == 0, result.stderr
    changelog = open_changelog().read()
    assert_content = f"# Changelog\n\n## 1.0.0 ({date.today().strftime('%Y-%m-%d')})\n\n#### Fixes\n\n* Some file fix\n"
    assert changelog == assert_content
    assert "Add file PRO-1" not in changelog


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "fix: Some file fix"',
            "git tag 1.0.0",
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_starting_commit_is_only_commit(runner, open_changelog):
    """JS test notes:
    * not supported by JS (see test above)
    """
    result = runner.invoke(main, ["--starting-commit", "1.0.0"])
    assert result.exit_code == 0, result.stderr
    changelog = open_changelog().read()
    assert_content = f"# Changelog\n\n## 1.0.0 ({date.today().strftime('%Y-%m-%d')})\n\n#### Fixes\n\n* Some file fix\n"
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "fix: Some file fix"',
            "git tag 1.0.0",
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_starting_commit_not_exist(test_repo, runner, open_changelog):
    """JS test notes:
    * not supported by JS (see test above)
    """
    result = runner.invoke(main, ["--starting-commit", "nonexist"])
    assert result.exit_code != 0, result.stderr


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file PRO-1"',
            "git tag stop",
            'git commit --allow-empty -q -m "fix: Some file fix"',
            "git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git",
        ]
    ],
)
def test_stopping_commit(runner, open_changelog):
    result = runner.invoke(main, ["--stopping-commit", "stop", "--unreleased"])
    assert result.exit_code == 0, result.stderr
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n* Add file PRO-1\n"
    )
    assert changelog == assert_content
    assert "Some file fix" not in changelog


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1\n\n"',
        ]
    ],
)
def test_empty_line_body(test_repo, runner, open_changelog):
    result = runner.invoke(main, ["--unreleased"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n* Add file #1\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1\n\nBody line"',
        ]
    ],
)
def test_single_line_body(runner, open_changelog):
    result = runner.invoke(main, ["--unreleased"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n* Add file #1\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [["git commit --allow-empty -q -m 'feat: Add file #1\n\nBody line 1\nBody line 2'"]],
)
def test_double_line_body(runner, open_changelog):
    result = runner.invoke(main, ["--unreleased"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    print(changelog)
    assert "Add file #1" in changelog


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1\n\nBody line 1\nBody line 2\nBody line 3"',
        ]
    ],
)
def test_triple_line_body(runner, open_changelog):
    result = runner.invoke(main, ["--unreleased"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n* Add file #1\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1\n\nBody paragraph 1\n\nBody paragraph 2"',
        ]
    ],
)
def test_multi_paragraph_body(runner, open_changelog):
    result = runner.invoke(main, ["--unreleased"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n* Add file #1\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1\n\nBody line\n\nFooter: first footer"',
        ]
    ],
)
def test_single_line_body_single_footer(runner, open_changelog):
    result = runner.invoke(main, ["--unreleased"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n* Add file #1\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat: Add file #1\n\nBody line\n\n'
            'Footer: first footer\nFooter: second footer"',
        ]
    ],
)
def test_single_line_body_double_footer(runner, open_changelog):
    result = runner.invoke(main, ["--unreleased"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n#### New Features\n\n* Add file #1\n"
    )
    assert changelog == assert_content


@pytest.mark.parametrize(
    "commands",
    [
        [
            'git commit --allow-empty -q -m "feat(scope): Add file #1\n\nBody line 1\nBody line 2\nBody line 3"',
        ]
    ],
)
def test_custom_template(path_project, runner, open_changelog):
    custom_template_path = os.path.join(path_project, "tests/custom_template/custom_template.jinja2")
    result = runner.invoke(main, ["--unreleased", "--template", custom_template_path])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    changelog = open_changelog().read()
    assert_content = (
        f"# Changelog\n\n## Unreleased ({date.today().strftime('%Y-%m-%d')})\n\n"
        f"#### New Features\n\n* **scope**: Add file #1\n"
    )
    assert changelog == assert_content


def test_custom_template_invalid_file(path_project, runner, caplog):
    result = runner.invoke(main, ["--template", "nonsense"])
    assert result.exit_code != 0, result.stderr


@pytest.mark.parametrize(
    "commands",
    [["git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git"]],
)
def test_debug(caplog, runner, open_changelog):
    caplog.set_level(logging.DEBUG)
    result = runner.invoke(main, ["--debug"])
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    assert "Logging level has been set to DEBUG" in caplog.text


@pytest.mark.parametrize(
    "commands",
    [["git remote add origin https://github.com/Michael-F-Bryan/auto-changelog.git"]],
)
def test_no_debug(caplog, runner, open_changelog):
    caplog.set_level(logging.DEBUG)
    result = runner.invoke(main)
    assert result.exit_code == 0, result.stderr
    assert result.output == ""
    assert "Logging level has been set to DEBUG" not in caplog.text
