#!/usr/bin/env python
'''
All things specific to Python (pyproject.toml, PyPI, etc.)
'''

import logging
import pathlib
import subprocess
import tomllib
from urllib.parse import quote as urlquote

import requests
import tomli_w
import tomli_w._writer
from spdx_license_list import LICENSES as SPDX_LICENSES
from trove_classifiers import classifiers as py_classifiers

from ..file import update_content
from .venv import virtual_environment

LOGGER = logging.getLogger(__name__)


def get_pypi_url(name):
    '''
    Get the URL to the project's page on PyPI if it exists.

    Args:
        name:
            The project name.

    Returns:
        The project URL, or None if it does not exist.
    '''
    timeout = 5
    url = f'https://pypi.org/pypi/{urlquote(name)}/json'
    LOGGER.debug('Querying %s', url)
    resp = requests.get(url, timeout=timeout).json()
    try:
        return resp['info']['package_url']
    except KeyError:
        return None


def get_license_classifier(spdx_id):
    '''
    Get the Python license classifier for the given SPDX license ID.

    Args:
        spdx_id:
            The SPXD license ID.

    Returns:
        The corresponding Python trove classifier if found, else None.
    '''
    spdx_data = SPDX_LICENSES.get(spdx_id)
    if not spdx_data:
        LOGGER.warning('%s is not a recognized SPDX license ID.', spdx_id)
        return None
    name = spdx_data.name
    if spdx_data.osi_approved:
        expected = f'License :: OSI Approved :: {name}'
    else:
        expected = 'License :: {name}'
    if expected in py_classifiers:
        return expected
    closest = None
    for classifier in py_classifiers:
        if classifier.startswith(expected):
            if closest is None or len(closest) > len(classifier):
                closest = classifier
    if closest is None:
        LOGGER.warning(
            'Failed to map SPDX license ID %s to a Python trove classifier.',
            spdx_id
        )
    return closest


def update_pyproject_toml(project):
    '''
    Update the URLs in a pyproject.toml file.

    Args:
        project:
            The Project instance.
    '''
    path = project.pyproject_toml_path
    data = tomllib.loads(path.read_text(encoding='utf-8'))

    urls = data['project']['urls']
    urls.clear()
    urls.update(project.urls)

    data['project']['authors'] = [
        {
            'name': f'{author["given-names"]} {author["family-names"]}',
            'email': author['email']
        }
        for author in project.config.config['authors']
    ]

    classifiers = set(
        classifier
        for classifier in data['project'].get('classifiers', [])
        if not classifier.startswith('License :: ')
    )

    spdx_id = project.spdx_license
    if spdx_id:
        license_classifier = get_license_classifier(spdx_id)
        if license_classifier:
            classifiers.add(license_classifier)
    else:
        LOGGER.warning('No license detected for project.')

    data['project']['classifiers'] = sorted(classifiers)

    content = tomli_w.dumps(data, multiline_strings=True)
    update_content(content, path)
    return urls


def get_version(project_dir):
    '''
    Get the version of a project from its directory.

    This will create a temporary virtual environment and install the project in
    it to get the version. This ensures that VCS-versions are correctly handled.

    This should not be necessary but at the time or writing the current version
    of CodeMetaPy fails to detect versions.
    '''
    project_dir = pathlib.Path(project_dir).resolve()
    with virtual_environment() as (_venv_dir, python_exe):
        cmd = [python_exe, '-m', 'pip', 'install', '--no-deps', '-U', str(project_dir)]
        LOGGER.debug('Installing package in virtual environment: %s', cmd)
        subprocess.run(cmd, check=True)

        data = tomllib.loads((project_dir / 'pyproject.toml').read_text(encoding='utf-8'))
        name = data['project']['name']
        cmd = [
            python_exe, '-c',
            f'from importlib.metadata import version; print(version("{name}"))'
        ]
        LOGGER.debug('Getting version of %s: %s', name, cmd)
        return subprocess.run(cmd, check=True, capture_output=True).stdout.decode().strip()
