import sys
import unittest
import sysconfig

from test.support import subTests
from test.support import import_helper

_testcapi = import_helper.import_module('_testcapi')


class Test_ABIInfo_Check(unittest.TestCase):
    @subTests('modname', (None, 'test_mod'))
    def test_zero(self, modname):
        _testcapi.pyabiinfo_check(modname, 0, 0, 0, 0, 0)
        _testcapi.pyabiinfo_check(modname, 1, 0, 0, 0, 0)

    def test_large_major_version(self):
        with self.assertRaisesRegex(ImportError,
                                    '^PyABIInfo version too high$'):
            _testcapi.pyabiinfo_check(None, 2, 0, 0, 0, 0)
        with self.assertRaisesRegex(ImportError,
                                    '^test_mod: PyABIInfo version too high$'):
            _testcapi.pyabiinfo_check("test_mod", 2, 0, 0, 0, 0)

    @subTests('modname', (None, 'test_mod'))
    def test_large_minor_version(self, modname):
        _testcapi.pyabiinfo_check(modname, 1, 2, 0, 0, 0)

    @subTests('modname', (None, 'test_mod'))
    @subTests('major', (0, 1))
    @subTests('minor', (0, 1, 9))
    @subTests('build', (0, sys.hexversion))
    def test_positive_regular(self, modname, major, minor, build):
        ver = sys.hexversion
        truncated = ver & 0xffff0000
        filled = truncated | 0x12b8
        maxed = truncated | 0xffff
        for abi_version in (0, ver, truncated, filled, maxed):
            with self.subTest(abi_version=abi_version):
                _testcapi.pyabiinfo_check(modname, major, minor, 0,
                                          build, abi_version)

    @subTests('modname', (None, 'test_mod'))
    @subTests('minor', (0, 1, 9))
    @subTests('build', (0, sys.hexversion))
    @subTests('offset', (+0x00010000, -0x00010000))
    def test_negative_regular(self, modname, minor, build, offset):
        ver = sys.hexversion + offset
        truncated = ver & 0xffff0000
        filled = truncated | 0x12b8
        maxed = truncated | 0xffff
        for abi_version in (ver, truncated, filled, maxed):
            with self.subTest(abi_version=abi_version):
                with self.assertRaisesRegex(
                        ImportError,
                        r'incompatible ABI version \(3\.\d+\)$'):
                    _testcapi.pyabiinfo_check(modname, 1, minor, 0,
                                              build,
                                              abi_version)

    @subTests('modname', (None, 'test_mod'))
    @subTests('major', (0, 1))
    @subTests('minor', (0, 1, 9))
    @subTests('build', (0, sys.hexversion))
    @subTests('abi_version', (
        0,
        0x03020000,
        sys.hexversion,
        sys.hexversion & 0xffff0000,
        sys.hexversion - 0x00010000,
    ))
    def test_positive_stable(self, modname, major, minor, build, abi_version):
        _testcapi.pyabiinfo_check(modname, major, minor,
                                  _testcapi.PyABIInfo_STABLE,
                                  build,
                                  abi_version)

    @subTests('modname', (None, 'test_mod'))
    @subTests('minor', (0, 1, 9))
    @subTests('build', (0, sys.hexversion))
    @subTests('abi_version_and_msg', (
        (1, 'invalid'),
        (3, 'invalid'),
        (0x0301ffff, 'invalid'),
        ((sys.hexversion & 0xffff0000) + 0x00010000, 'incompatible future'),
        (sys.hexversion + 0x00010000, 'incompatible future'),
        (0x04000000, 'incompatible future'),
    ))
    def test_negative_stable(self, modname, minor, build, abi_version_and_msg):
        abi_version, msg = abi_version_and_msg
        with self.assertRaisesRegex(
                ImportError,
                rf'{msg} stable ABI version \(\d+\.\d+\)$'):
            _testcapi.pyabiinfo_check(modname, 1, minor,
                                      _testcapi.PyABIInfo_STABLE,
                                      build,
                                      abi_version)

    @subTests('modname', (None, 'test_mod'))
    @subTests('major', (0, 1))
    @subTests('minor', (0, 1, 9))
    @subTests('build', (0, sys.hexversion))
    @subTests('abi_version', (0, sys.hexversion))
    def test_positive_internal(self, modname, major, minor, build, abi_version):
        _testcapi.pyabiinfo_check(modname, major, minor,
                                  _testcapi.PyABIInfo_INTERNAL,
                                  build,
                                  abi_version)

    @subTests('modname', (None, 'test_mod'))
    @subTests('minor', (0, 1, 9))
    @subTests('build', (0, sys.hexversion))
    @subTests('abi_version', (
        sys.hexversion - 0x00010000,
        sys.hexversion - 1,
        sys.hexversion + 1,
        sys.hexversion + 0x00010000,
    ))
    def test_negative_internal(self, modname, minor, build, abi_version):
        with self.assertRaisesRegex(
                ImportError,
                r'incompatible internal ABI \(0x[\da-f]+ != 0x[\da-f]+\)$'):
            _testcapi.pyabiinfo_check(modname, 1, minor,
                                      _testcapi.PyABIInfo_INTERNAL,
                                      build,
                                      abi_version)

    @subTests('modname', (None, 'test_mod'))
    @subTests('minor', (0, 1, 9))
    @subTests('build', (0, sys.hexversion))
    @subTests('ft_flag', (
        0,
        (_testcapi.PyABIInfo_FREETHREADED
            if sysconfig.get_config_var("Py_GIL_DISABLED")
            else _testcapi.PyABIInfo_GIL),
        _testcapi.PyABIInfo_FREETHREADING_AGNOSTIC,
    ))
    def test_positive_freethreading(self, modname, minor, build, ft_flag):
        self.assertEqual(ft_flag & _testcapi.PyABIInfo_FREETHREADING_AGNOSTIC,
                         ft_flag)
        _testcapi.pyabiinfo_check(modname, 1, minor, ft_flag, build, 0)

    @subTests('modname', (None, 'test_mod'))
    @subTests('minor', (0, 1, 9))
    @subTests('build', (0, sys.hexversion))
    def test_negative_freethreading(self, modname, minor, build):
        if sysconfig.get_config_var("Py_GIL_DISABLED"):
            ft_flag = _testcapi.PyABIInfo_GIL
            msg = "incompatible with free-threaded CPython"
        else:
            ft_flag = _testcapi.PyABIInfo_FREETHREADED
            msg = "only compatible with free-threaded CPython"
        with self.assertRaisesRegex(ImportError, msg):
            _testcapi.pyabiinfo_check(modname, 1, minor, ft_flag, build, 0)


class TestModsupport(unittest.TestCase):
    def test_pyarg_parsearray(self):
        func = _testcapi.pyarg_parsearray
        self.assertEqual(func(1, 2), (1, 2, 0))
        self.assertEqual(func(1, 2, 3), (1, 2, 3))
        self.assertRaises(TypeError, func, 1)
        self.assertRaises(TypeError, func, "str", 2)

    def test_funcandkeywords(self):
        func = _testcapi.pyarg_parsearrayandkeywords
        self.assertEqual(func(1, 2), (1, 2, 0))
        self.assertEqual(func(1, 2, 3), (1, 2, 3))
        self.assertEqual(func(1, b=2), (1, 2, 0))
        self.assertEqual(func(1, b=2, c=3), (1, 2, 3))
        self.assertRaises(TypeError, func, 1)
        self.assertRaises(TypeError, func, "str", 2)
        self.assertRaises(TypeError, func, 1, z=2)
