"""Tests of the VCS module."""

import subprocess
from pathlib import Path

import pytest
from pytest import param, LogCaptureFixture

from bumpversion import scm
from bumpversion.exceptions import DirtyWorkingDirectoryError
from bumpversion.ui import setup_logging
from tests.conftest import get_config_data, inside_dir


def test_git_is_usable(git_repo: Path) -> None:
    """Should return true if git is available, and it is a git repo."""
    with inside_dir(git_repo):
        assert scm.Git.is_usable()


def test_git_is_not_usable(tmp_path: Path) -> None:
    """Should return false if it is not a git repo."""
    with inside_dir(tmp_path):
        assert not scm.Git.is_usable()


def test_git_asserts_not_dirty(git_repo: Path) -> None:
    """If the git repo is clean, assert_notdirty should return true."""
    with inside_dir(git_repo):
        scm.Git.assert_nondirty()


def test_git_asserts_dirty(git_repo: Path) -> None:
    """If the git repo has modified files, assert_notdirty should return false."""
    readme = git_repo.joinpath("readme.md")
    readme.touch()
    with pytest.raises(DirtyWorkingDirectoryError):
        with inside_dir(git_repo):
            subprocess.run(["git", "add", "readme.md"])
            scm.Git.assert_nondirty()


def test_git_latest_tag_info(git_repo: Path) -> None:
    """Should return information about the latest tag."""
    readme = git_repo.joinpath("readme.md")
    readme.touch()
    tag_prefix = "app/"
    parse_pattern = r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"
    tag_name = f"{tag_prefix}{{new_version}}"
    with inside_dir(git_repo):
        assert scm.Git.latest_tag_info(tag_name, parse_pattern=parse_pattern) == scm.SCMInfo(tool=scm.Git)

        # Add a file and tag
        subprocess.run(["git", "add", "readme.md"])
        subprocess.run(["git", "commit", "-m", "first"])
        subprocess.run(["git", "tag", f"{tag_prefix}0.1.0"])
        tag_info = scm.Git.latest_tag_info(tag_name, parse_pattern=parse_pattern)
        assert tag_info.commit_sha is not None
        assert tag_info.current_version == "0.1.0"
        assert tag_info.distance_to_latest_tag == 0

        # Make it dirty
        git_repo.joinpath("something.md").touch()
        subprocess.run(["git", "add", "something.md"])
        tag_info = scm.Git.latest_tag_info(tag_name, parse_pattern=parse_pattern)
        assert tag_info.commit_sha is not None
        assert tag_info.current_version == "0.1.0"
        assert tag_info.distance_to_latest_tag == 0
        assert tag_info.dirty is True


def test_git_detects_existing_tag(git_repo: Path, caplog: LogCaptureFixture) -> None:
    """Attempting to tag when a tag exists will do nothing."""
    # Arrange
    git_repo.joinpath("readme.md").touch()
    git_repo.joinpath("something.md").touch()
    overrides = {"current_version": "0.1.0", "commit": True, "tag": True}
    context = {
        "current_version": "0.1.0",
        "new_version": "0.2.0",
    }
    setup_logging(2)
    with inside_dir(git_repo):
        conf, version_config, current_version = get_config_data(overrides)
        subprocess.run(["git", "add", "readme.md"])
        subprocess.run(["git", "commit", "-m", "first"])
        subprocess.run(["git", "tag", "v0.1.0"])
        subprocess.run(["git", "add", "something.md"])
        subprocess.run(["git", "commit", "-m", "second"])
        subprocess.run(["git", "tag", "v0.2.0"])

        # Act
        scm.Git.tag_in_scm(config=conf, context=context)

    # Assert
    assert "Will not tag" in caplog.text


def test_hg_is_not_usable(tmp_path: Path) -> None:
    """Should return false if it is not a mercurial repo."""
    with inside_dir(tmp_path):
        assert not scm.Mercurial.is_usable()


def test_hg_is_usable(hg_repo: Path) -> None:
    """Should return false if it is not a mercurial repo."""
    with inside_dir(hg_repo):
        assert scm.Mercurial.is_usable()


@pytest.mark.parametrize(
    ["repo", "scm_command", "scm_class"],
    [
        param("git_repo", "git", scm.Git, id="git"),
        param("hg_repo", "hg", scm.Mercurial, id="hg"),
    ],
)
def test_commit_and_tag_from_below_scm_root(repo: str, scm_command: str, scm_class: scm.SourceCodeManager, request):
    # Arrange
    repo_path: Path = request.getfixturevalue(repo)
    version_path = repo_path / "VERSION"
    version_path.write_text("30.0.3", encoding="utf-8")
    sub_dir_path = repo_path / "subdir"
    sub_dir_path.mkdir(exist_ok=True)

    overrides = {"current_version": "30.0.3", "commit": True, "tag": True, "files": [{"filename": str(version_path)}]}
    context = {
        "current_version": "30.0.3",
        "new_version": "30.1.0",
    }
    with inside_dir(repo_path):
        conf, version_config, current_version = get_config_data(overrides)
        subprocess.run([scm_command, "add", "VERSION"], check=True, capture_output=True)
        subprocess.run([scm_command, "commit", "-m", "initial commit"], check=True, capture_output=True)
        with inside_dir(sub_dir_path):
            version_path.write_text("30.1.0", encoding="utf-8")

            # Act
            scm_class.commit_to_scm(files=[version_path], config=conf, context=context)
            scm_class.tag_in_scm(config=conf, context=context)

        # Assert
        tag_info = scm_class.latest_tag_info(conf.tag_name, parse_pattern=conf.parse)
        if scm_command == "git":
            assert tag_info.commit_sha is not None
            assert tag_info.distance_to_latest_tag == 0
        assert tag_info.current_version == "30.1.0"
        assert tag_info.dirty is False


# write tests for no-commit, no-tag and dry-run
@pytest.mark.parametrize(
    ["repo", "scm_command", "scm_class"],
    [
        param("git_repo", "git", scm.Git, id="git"),
        param("hg_repo", "hg", scm.Mercurial, id="hg"),
    ],
)
@pytest.mark.parametrize(
    ["commit", "tag", "dry_run", "should_commit", "should_tag"],
    [
        param(True, True, True, False, False, id="dry-run-stops-commit-and-tag"),
        param(True, False, False, True, False, id="commit-no-tag"),
        param(False, True, False, False, True, id="no-commit-will-tag"),
    ],
)
def test_commit_tag_dry_run_interactions(
    repo: str,
    scm_command: str,
    scm_class: scm.SourceCodeManager,
    commit: bool,
    tag: bool,
    dry_run: bool,
    should_commit: bool,
    should_tag: bool,
    caplog: LogCaptureFixture,
    request,
):
    """Combinations of commit, tag, dry-run and should produce the expected results."""
    # Arrange
    repo_path: Path = request.getfixturevalue(repo)
    version_path = repo_path / "VERSION"
    version_path.write_text("30.0.3", encoding="utf-8")

    overrides = {"current_version": "30.0.3", "commit": commit, "tag": tag, "files": [{"filename": str(version_path)}]}
    context = {
        "current_version": "30.0.3",
        "new_version": "30.1.0",
    }
    with inside_dir(repo_path):
        conf, version_config, current_version = get_config_data(overrides)
        subprocess.run([scm_command, "add", "VERSION"], check=True, capture_output=True)
        subprocess.run([scm_command, "commit", "-m", "initial commit"], check=True, capture_output=True)
        version_path.write_text("30.1.0", encoding="utf-8")
        # Act
        scm_class.commit_to_scm(files=[version_path], config=conf, context=context, dry_run=dry_run)
        scm_class.tag_in_scm(config=conf, context=context, dry_run=dry_run)

        # Assert
        if commit and dry_run:
            assert "Would commit" in caplog.text
        elif should_commit:
            assert "Committing " in caplog.text
        else:
            assert "Would not commit" in caplog.text

        if tag and dry_run:
            assert "Would tag" in caplog.text
        elif should_tag:
            assert "Tagging " in caplog.text
        else:
            assert "Would not tag" in caplog.text
