from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from commitizen.providers import get_provider
from commitizen.providers.scm_provider import ScmProvider
from tests.utils import (
    create_branch,
    create_file_and_commit,
    create_tag,
    merge_branch,
    switch_branch,
)

if TYPE_CHECKING:
    from commitizen.config.base_config import BaseConfig


@pytest.mark.parametrize(
    "tag_format,tag,expected_version",
    (
        # If tag_format is $version (the default), version_scheme.parser is used.
        # Its DEFAULT_VERSION_PARSER allows a v prefix, but matches PEP440 otherwise.
        ("$version", "no-match-because-version-scheme-is-strict", "0.0.0"),
        ("$version", "0.1.0", "0.1.0"),
        ("$version", "v0.1.0", "0.1.0"),
        ("$version", "v-0.1.0", "0.0.0"),
        # If tag_format is not None or $version, TAG_FORMAT_REGEXS are used, which are
        # much more lenient but require a v prefix.
        ("v$version", "v0.1.0", "0.1.0"),
        ("v$version", "no-match-because-no-v-prefix", "0.0.0"),
        # no match because not a valid version
        ("v$version", "v-match-TAG_FORMAT_REGEXS", "0.0.0"),
        ("version-$version", "version-0.1.0", "0.1.0"),
        ("version-$version", "version-0.1", "0.1"),
        ("version-$version", "version-0.1.0rc1", "0.1.0rc1"),
        ("v$minor.$major.$patch", "v1.0.0", "0.1.0"),
        ("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"),
        ("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"),
        ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"),
        ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"),
        ("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"),
    ),
)
@pytest.mark.usefixtures("tmp_git_project")
def test_scm_provider(
    config: BaseConfig, tag_format: str, tag: str, expected_version: str
):
    create_file_and_commit("test: fake commit")
    create_tag(tag)
    create_file_and_commit("test: fake commit")
    create_tag("should-not-match")

    config.settings["version_provider"] = "scm"
    config.settings["tag_format"] = tag_format

    provider = get_provider(config)
    assert isinstance(provider, ScmProvider)
    actual_version = provider.get_version()
    assert actual_version == expected_version

    # Should not fail on set_version()
    provider.set_version("43.1")


@pytest.mark.usefixtures("tmp_git_project")
def test_scm_provider_default_without_commits_and_tags(config: BaseConfig):
    config.settings["version_provider"] = "scm"

    provider = get_provider(config)
    assert isinstance(provider, ScmProvider)
    assert provider.get_version() == "0.0.0"


@pytest.mark.usefixtures("tmp_git_project")
def test_scm_provider_default_with_commits_and_tags(config: BaseConfig):
    config.settings["version_provider"] = "scm"

    provider = get_provider(config)
    assert isinstance(provider, ScmProvider)
    assert provider.get_version() == "0.0.0"

    create_file_and_commit("Initial state")
    create_tag("1.0.0")
    # create develop
    create_branch("develop")
    switch_branch("develop")

    # add a feature to develop
    create_file_and_commit("develop: add beta feature1")
    assert provider.get_version() == "1.0.0"
    create_tag("1.1.0b0")

    # create staging
    create_branch("staging")
    switch_branch("staging")
    create_file_and_commit("staging: Starting release candidate")
    assert provider.get_version() == "1.1.0b0"
    create_tag("1.1.0rc0")

    # add another feature to develop
    switch_branch("develop")
    create_file_and_commit("develop: add beta feature2")
    assert provider.get_version() == "1.1.0b0"
    create_tag("1.2.0b0")

    # add a hotfix to master
    switch_branch("master")
    create_file_and_commit("master: add hotfix")
    assert provider.get_version() == "1.0.0"
    create_tag("1.0.1")

    # merge the hotfix to staging
    switch_branch("staging")
    merge_branch("master")

    assert provider.get_version() == "1.1.0rc0"


@pytest.mark.usefixtures("tmp_git_project")
def test_scm_provider_detect_legacy_tags(config: BaseConfig):
    config.settings["version_provider"] = "scm"
    config.settings["tag_format"] = "v${version}"
    config.settings["legacy_tag_formats"] = [
        "legacy-${version}",
        "old-${version}",
    ]
    provider = get_provider(config)

    create_file_and_commit("test: fake commit")
    create_tag("old-0.4.1")
    assert provider.get_version() == "0.4.1"

    create_file_and_commit("test: fake commit")
    create_tag("legacy-0.4.2")
    assert provider.get_version() == "0.4.2"

    create_file_and_commit("test: fake commit")
    create_tag("v0.5.0")
    assert provider.get_version() == "0.5.0"
