# SPDX-FileCopyrightText: 2021 The meson-python developers
#
# SPDX-License-Identifier: MIT

import importlib.machinery
import os
import pathlib
import platform
import sys
import sysconfig

from collections import defaultdict
from unittest.mock import Mock

import packaging.tags
import pytest

import mesonpy
import mesonpy._tags

from .conftest import FREE_THREADED_BUILD, adjust_packaging_platform_tag


# Test against the wheel tag generated by packaging module.
tag = next(packaging.tags.sys_tags())
ABI = tag.abi
INTERPRETER = tag.interpreter
PLATFORM = adjust_packaging_platform_tag(tag.platform)


def get_abi3_suffix():
    """Get the filename suffix identifying stable ABI extension modules."""

    # EXTENSION_SUFFIXES is ordered from the more to the less specific ABI.
    # On Python 3.15 or later, this resolves to ``.abi3t-${arch}`` for
    # free-threaded builds and to ``.abi3-${arch}`` on regular builds.
    #
    # Older Python versions do not support a stable ABI for free-threaded
    # builds, and do not support a platform-specific filename suffix, and
    # this resolves to simply ``.abi3``.
    #
    # On Windows, Python does not use a dedicated filename suffix for stable
    # ABI extension modules, and this resolves to ``.pyd``.
    #
    # Returns None on platforms not supporting the stable ABI (notably PyPy).

    for suffix in importlib.machinery.EXTENSION_SUFFIXES:
        if suffix.startswith('.abi3'):
            return suffix
        if suffix == '.pyd':
            return suffix
    return None


SUFFIX = sysconfig.get_config_var('EXT_SUFFIX')
ABI3SUFFIX = get_abi3_suffix()
STABLE_ABI_KIND = 'abi3t' if FREE_THREADED_BUILD and sys.version_info >= (3, 15) else 'abi3'


def test_wheel_tag():
    assert str(mesonpy._tags.Tag()) == f'{INTERPRETER}-{ABI}-{PLATFORM}'
    assert str(mesonpy._tags.Tag(abi='abi3')) == f'{INTERPRETER}-abi3-{PLATFORM}'


@pytest.mark.skipif(sys.platform != 'darwin', reason='macOS specific test')
def test_macos_platform_tag(monkeypatch):
    for minor in range(9, 16):
        monkeypatch.setenv('MACOSX_DEPLOYMENT_TARGET', f'10.{minor}')
        version = (10, minor) if platform.mac_ver()[2] != 'arm64' else (11, 0)
        assert next(packaging.tags.mac_platforms(version)) == mesonpy._tags.get_platform_tag()
    for major in range(11, 20):
        for minor in range(3):
            monkeypatch.setenv('MACOSX_DEPLOYMENT_TARGET', f'{major}.{minor}')
            assert next(packaging.tags.mac_platforms((major, minor))) == mesonpy._tags.get_platform_tag()
    for major in range(11, 13):
        monkeypatch.setenv('MACOSX_DEPLOYMENT_TARGET', f'{major}.0')
        assert next(packaging.tags.mac_platforms((major, 0))) == mesonpy._tags.get_platform_tag()
        monkeypatch.setenv('MACOSX_DEPLOYMENT_TARGET', f'{major}')
        assert next(packaging.tags.mac_platforms((major, 0))) == mesonpy._tags.get_platform_tag()


@pytest.mark.skipif(sys.platform != 'darwin', reason='macOS specific test')
def test_macos_platform_tag_arm64(monkeypatch):
    monkeypatch.setenv('_PYTHON_HOST_PLATFORM', 'macosx-12.0-arm64')
    # Verify that the minimum platform ABI version on arm64 is 11.0.
    monkeypatch.setenv('MACOSX_DEPLOYMENT_TARGET', '10.12')
    assert mesonpy._tags.get_platform_tag() == 'macosx_11_0_arm64'
    monkeypatch.setenv('MACOSX_DEPLOYMENT_TARGET', '12.34')
    assert mesonpy._tags.get_platform_tag() == 'macosx_12_0_arm64'


@pytest.mark.skipif(sys.platform != 'darwin', reason='macOS specific test')
def test_python_host_platform(monkeypatch):
    monkeypatch.setenv('_PYTHON_HOST_PLATFORM', 'macosx-12.0-arm64')
    assert mesonpy._tags.get_platform_tag().endswith('arm64')
    monkeypatch.setenv('_PYTHON_HOST_PLATFORM', 'macosx-11.1-x86_64')
    assert mesonpy._tags.get_platform_tag().endswith('x86_64')


@pytest.mark.skipif(sys.version_info < (3, 13), reason='requires Python 3.13 or higher')
@pytest.mark.skipif(sys.platform != 'darwin', reason='macOS specific test')
def test_ios_platform_tag(monkeypatch):
    # Mock being on iOS
    monkeypatch.setattr(sys.implementation, '_multiarch', 'arm64-iphoneos')
    monkeypatch.setattr(sysconfig, 'get_platform', Mock(return_value='ios-13.0-arm64-iphoneos'))
    ios_ver = platform.IOSVersionInfo('iOS', '13.0', 'iPhone', False)
    monkeypatch.setattr(platform, 'ios_ver', Mock(return_value=ios_ver))

    # Check the default value
    assert next(packaging.tags.ios_platforms((13, 0))) == mesonpy._tags.get_platform_tag()

    # Check the value when IPHONEOS_DEPLOYMENT_TARGET is set.
    for major in range(13, 20):
        for minor in range(3):
            monkeypatch.setenv('IPHONEOS_DEPLOYMENT_TARGET', f'{major}.{minor}')
            assert next(packaging.tags.ios_platforms((major, minor))) == mesonpy._tags.get_platform_tag()


def wheel_builder_test_factory(content, pure=True, limited_api=False):
    manifest = defaultdict(list)
    for key, value in content.items():
        manifest[key] = [mesonpy._Entry(pathlib.Path(x), os.path.join('build', x)) for x in value]
    return mesonpy._WheelBuilder(None, manifest, limited_api, False)


def test_tag_empty_wheel():
    builder = wheel_builder_test_factory({})
    assert str(builder.tag) == 'py3-none-any'


def test_tag_purelib_wheel():
    builder = wheel_builder_test_factory({
        'purelib': ['pure.py'],
    })
    assert str(builder.tag) == 'py3-none-any'


def test_tag_platlib_wheel():
    builder = wheel_builder_test_factory({
        'platlib': [f'extension{SUFFIX}'],
    })
    assert str(builder.tag) == f'{INTERPRETER}-{ABI}-{PLATFORM}'


def test_tag_stable_abi():
    builder = wheel_builder_test_factory({
        'platlib': [f'extension{ABI3SUFFIX or SUFFIX}'],
    }, limited_api=True)
    # PyPy does not support the stable ABI.
    if not ABI3SUFFIX:
        abi = ABI
    elif FREE_THREADED_BUILD and sys.version_info >= (3, 15):
        abi = 'abi3.abi3t'
    else:
        abi = 'abi3'
    assert str(builder.tag) == f'{INTERPRETER}-{abi}-{PLATFORM}'


@pytest.mark.xfail(not ABI3SUFFIX, reason='interpreter does not support the stable ABI')
def test_tag_mixed_abi():
    builder = wheel_builder_test_factory({
        'platlib': [f'extension{ABI3SUFFIX or SUFFIX}', f'another{SUFFIX}'],
    }, pure=False, limited_api=True)
    with pytest.raises(mesonpy.BuildError, match='The package declares compatibility with Python limited API but '):
        assert str(builder.tag) == f'{INTERPRETER}-abi3-{PLATFORM}'


@pytest.mark.skipif(not ABI3SUFFIX, reason='interpreter does not support the stable ABI')
def test_tag_stable_abi_multiarch():
    # Python 3.15 and later append the multiarch tuple to the stable ABI
    # filename suffix. Verify that it is ignored rather than causing the
    # module to be rejected: when cross compiling it differs from the one
    # of the build interpreter.
    builder = wheel_builder_test_factory({
        'platlib': [f'extension.{STABLE_ABI_KIND}-aarch64-linux-gnu.so'],
    }, pure=False, limited_api=True)
    abi = 'abi3.abi3t' if STABLE_ABI_KIND == 'abi3t' else 'abi3'
    assert str(builder.tag) == f'{INTERPRETER}-{abi}-{PLATFORM}'
