# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Generate a pip requirements file pinning build and runtime dependencies
to their minimum supported versions, as declared in pyproject.toml.

By default, reads [build-system].requires and [project].dependencies and
writes <project>_oldest_deps.txt. With --all, also includes the "all"
extra and writes <project>_oldest_deps-all.txt.
"""

import argparse
import sys
import tomllib
from pathlib import Path

from packaging.requirements import Requirement
from packaging.version import Version

script_name = Path(__file__).name
out_path = Path(__file__).parent
pyproject = out_path.parent / 'pyproject.toml'
data = tomllib.loads(pyproject.read_text())


def pin_minimum(req_str_sections):
    """
    Return a list of pinned requirement strings with the minimum
    versions for each package.

    Parameters
    ----------
    req_str_sections : list of list of str
        A list of sections, where each section is a list of
        requirement strings (e.g. from [build-system].requires or
        [project].dependencies).

    Returns
    -------
    result : list of str
        A list of requirement strings with minimum versions pinned (e.g.
        'package==1.2.3').
    """
    mins = {}
    for section in req_str_sections:
        for req_str in section:
            req = Requirement(req_str)
            name = req.name.lower()
            for spec in req.specifier:
                if spec.operator == '>=':
                    ver = Version(spec.version)
                    if name not in mins or ver > mins[name][1]:
                        mins[name] = (req.name, ver)
                    break
    return [f'{name}=={ver}' for name, ver in mins.values()]


parser = argparse.ArgumentParser(
    description=('Generate oldest-dependency constraint files from '
                 'pyproject.toml.'),
    epilog=f'Example: python {script_name} --all',
)
parser.add_argument(
    '--all',
    action='store_true',
    dest='include_all',
    help='also include the "all" extras in install constraints',
)
parser.add_argument(
    '--path',
    type=Path,
    default=out_path,
    metavar='DIR',
    help=('directory where the output file will be written '
          '(default: %(default)s)'),
)

args = parser.parse_args()

# Include project name in basename
basename = data['project']['name'].replace('-', '_') + '_oldest_deps'

sections = [data['build-system']['requires'], data['project']['dependencies']]
if args.include_all:
    sections.append(data['project']['optional-dependencies']['all'])
    filename = f'{basename}-all.txt'
else:
    filename = f'{basename}.txt'

pins = pin_minimum(sections)
output = args.path / filename
header = ('# This file was generated by pin_minimum_requirements.py.\n'
          '# It pins the minimum supported versions of dependencies as '
          'declared in\n# pyproject.toml, including "build-system" '
          'dependencies (e.g., Cython).\n')
output.write_text(header + '\n'.join(pins) + '\n')
sys.stdout.write(f'Wrote {output}:\n')
for p in pins:
    sys.stdout.write(f'  {p}\n')
