"""
Test the checks for required versions
=====================================

**Module:** ``tests.test_version_checks``

Checking the versions this plugin depends on is crucial for the correct function.
"""

import sys
from typing import List, Optional, Tuple, Type, Union

import pytest
from attr.exceptions import PythonTooOldError
from pytest_mock import MockerFixture

from _ptcsvp.version import check_pytest_version, check_python_version


def build_version(p_version: str) -> Tuple[Union[int, str], ...]:
    """
    Test helper method: Build a version Tuple of a given version string. It is used by the
    :meth:`~tests.test_version_check.test_python_version` test case.

    :param p_version: Version string
    :returns: The version as tuple
    """
    elements: List[Union[int, str]] = []
    for v_part in p_version.split("."):
        try:
            elements.append(int(v_part))
        except ValueError:
            elements.append(v_part)
    return tuple(elements)


@pytest.mark.parametrize(
    ["p_version", "expect_error"],
    [
        ("3.8", None),
        ("3.8.1", None),
        ("3.8.10", None),
        ("3.8.10.ray.x", None),
        ("3.9", None),
        ("3.9.2", None),
        ("3.9.21", None),
        ("3.9.21.pre.7", None),
        ("3.10", None),
        ("3.10.31", None),
        ("3.10.31.beta.7", None),
        ("3.11", None),
        ("3.11.12", None),
        ("3.11.12.alpha.4", None),
        ("3.12", None),
        ("3.12.22", None),
        ("3.12.22.final", None),
        ("3.12.22.final.0", None),
        ("2.7", (PythonTooOldError, "At least Python 3.8 required")),
        ("2.7.1", (PythonTooOldError, "At least Python 3.8 required")),
        ("2.7.9", (PythonTooOldError, "At least Python 3.8 required")),
        ("2.7.9.final", (PythonTooOldError, "At least Python 3.8 required")),
        ("2.7.9.final.6", (PythonTooOldError, "At least Python 3.8 required")),
        ("3.7", (PythonTooOldError, "At least Python 3.8 required")),
        ("3.7.1", (PythonTooOldError, "At least Python 3.8 required")),
        ("3.7.final", (PythonTooOldError, "At least Python 3.8 required")),
        ("3.7.final.6", (PythonTooOldError, "At least Python 3.8 required")),
        ("3.7.final.6.777", (PythonTooOldError, "At least Python 3.8 required")),
        ("3.7.3.final.6.777", (PythonTooOldError, "At least Python 3.8 required")),
        ("3.7.3.7.final.6.777", (PythonTooOldError, "At least Python 3.8 required")),
        ("3", (PythonTooOldError, "At least Python 3.8 required")),
    ],
)
def test_python_version(
    mocker: MockerFixture, p_version: str, expect_error: Optional[Tuple[Type[Exception], str]]
) -> None:
    """
    Test if the python version is correctly recognized and if old versions raise an exception. This tests mainly the
    :meth:`_ptcsvp.version.check_python_version` method and is parametrized with a lot of different version combos.

    This test uses mocking, and the :meth:`~tests.test_version_check.build_version` helper method.

    :param mocker: Mocking fixture
    :param p_version: Version string
    :param expect_error: Expected error for the given version
    """
    mocker.patch.object(sys, "version_info", build_version(p_version))
    if expect_error is not None:
        with pytest.raises(expect_error[0]) as raised_exc:
            check_python_version()
        assert raised_exc.value.args[0] == expect_error[1]
    else:
        check_python_version()


@pytest.mark.parametrize(
    ["p_version", "expect_error"],
    [
        ("7.4", None),
        ("7.4.1", None),
        ("7.4.1.2", None),
        ("7.4.1.2.3", None),
        ("7.4.1.455555", None),
        ("7.4.1.2-145", None),
        ("7.4.1.090909090990", None),
        ("7.5", None),
        ("7.5.5", None),
        ("7.5.5.6", None),
        ("7.5.5.6.3333333", None),
        ("7.5.5.6.333333333.4", None),
        ("8.0", None),
        ("8.0.1", None),
        ("8.0.beta", None),
        ("8.0.beta.3", None),
        ("8.0.2.3", None),
        ("7.0", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.0.1", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.0.111111", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.1", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.1.1", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.1.111111", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.2", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.2.1", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.2.111111", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.3", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.3.1", (RuntimeError, "At least Pytest 7.4 required")),
        ("7.3.111111", (RuntimeError, "At least Pytest 7.4 required")),
        ("7", (RuntimeError, "At least Pytest 7.4 required")),
        ("6", (RuntimeError, "At least Pytest 7.4 required")),
        ("6.1", (RuntimeError, "At least Pytest 7.4 required")),
        ("6.1.2", (RuntimeError, "At least Pytest 7.4 required")),
    ],
)
def test_pytest_version(
    mocker: MockerFixture, p_version: str, expect_error: Optional[Tuple[Type[Exception], str]]
) -> None:
    """
    Test if the pytest version is correctly recognized and if a too old version raises an exception. This test focuses
    on the :meth:`_ptcsvp.version.check_pytest_version` method and is parametrized with a lot of different version
    strings.

    This test uses mocking.

    :param mocker: Mocking fixture
    :param p_version: Version string
    :param expect_error: Expected error and error message
    """
    mocker.patch.object(pytest, "__version__", p_version)
    assert pytest.__version__ == p_version
    if expect_error is not None:
        with pytest.raises(expect_error[0]) as raised_exc:
            check_pytest_version()
        assert raised_exc.value.args[0] == expect_error[1]
    else:
        check_pytest_version()
