# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import os
from contextlib import contextmanager
from optparse import OptionParser
from tempfile import NamedTemporaryFile

import pytest

from pex.bin.pex import build_pex, configure_clp, configure_clp_pex_resolution
from pex.common import safe_copy, temporary_dir
from pex.compatibility import nested, to_bytes
from pex.platforms import Platform
from pex.testing import (
    PY27,
    built_wheel,
    ensure_python_interpreter,
    run_pex_command,
    run_simple_pex
)


@contextmanager
def option_parser():
  yield OptionParser()


def test_clp_no_pypi_option():
  with option_parser() as parser:
    configure_clp_pex_resolution(parser)
    options, _ = parser.parse_args(args=[])
    assert len(options.indexes) == 1
    options, _ = parser.parse_args(args=['--no-pypi'])
    assert len(options.indexes) == 0, '--no-pypi should remove the pypi index.'


def test_clp_pypi_option_duplicate():
  with option_parser() as parser:
    configure_clp_pex_resolution(parser)
    options, _ = parser.parse_args(args=[])
    assert len(options.indexes) == 1
    options2, _ = parser.parse_args(args=['--pypi'])
    assert len(options2.indexes) == 1
    assert options.indexes == options2.indexes


def test_clp_find_links_option():
  with option_parser() as parser:
    configure_clp_pex_resolution(parser)
    options, _ = parser.parse_args(args=['-f', 'http://www.example.com'])
    assert len(options.indexes) == 1
    assert len(options.find_links) == 1


def test_clp_index_option():
  with option_parser() as parser:
    configure_clp_pex_resolution(parser)
    options, _ = parser.parse_args(args=[])
    assert len(options.indexes) == 1
    options2, _ = parser.parse_args(args=['-i', 'http://www.example.com'])
    assert len(options.indexes) == 2
    assert options2.indexes[0] == options.indexes[0]
    assert options2.indexes[1] == 'http://www.example.com'


def test_clp_index_option_render():
  with option_parser() as parser:
    configure_clp_pex_resolution(parser)
    options, _ = parser.parse_args(args=['--index', 'http://www.example.com'])
    assert ['https://pypi.org/simple', 'http://www.example.com'] == [
      str(idx) for idx in options.indexes
    ]


def test_clp_build_precedence():
  with option_parser() as parser:
    configure_clp_pex_resolution(parser)

    options, _ = parser.parse_args(args=['--no-build'])
    assert not options.build
    options, _ = parser.parse_args(args=['--build'])
    assert options.build

    options, _ = parser.parse_args(args=['--no-wheel'])
    assert not options.use_wheel

    options, _ = parser.parse_args(args=['--wheel'])
    assert options.use_wheel


# Make sure that we're doing append and not replace
def test_clp_requirements_txt():
  parser = configure_clp()
  options, _ = parser.parse_args(args='-r requirements1.txt -r requirements2.txt'.split())
  assert options.requirement_files == ['requirements1.txt', 'requirements2.txt']


def test_clp_constraints_txt():
  parser = configure_clp()
  options, _ = parser.parse_args(args='--constraint requirements1.txt'.split())
  assert options.constraint_files == ['requirements1.txt']


def test_clp_preamble_file():
  with NamedTemporaryFile() as tmpfile:
    tmpfile.write(to_bytes('print "foo!"'))
    tmpfile.flush()

    parser = configure_clp()
    options, reqs = parser.parse_args(args=['--preamble-file', tmpfile.name])
    assert options.preamble_file == tmpfile.name

    pex_builder = build_pex(reqs, options)
    assert pex_builder._preamble == 'print "foo!"'


def test_clp_prereleases():
  with option_parser() as parser:
    configure_clp_pex_resolution(parser)

    options, _ = parser.parse_args(args=[])
    assert not options.allow_prereleases

    options, _ = parser.parse_args(args=['--no-pre'])
    assert not options.allow_prereleases

    options, _ = parser.parse_args(args=['--pre'])
    assert options.allow_prereleases


def test_clp_prereleases_resolver():
  with nested(built_wheel(name='prerelease-dep', version='1.2.3b1'),
              built_wheel(name='transitive-dep', install_reqs=['prerelease-dep']),
              built_wheel(name='dep', install_reqs=['prerelease-dep>=1.2', 'transitive-dep']),
              temporary_dir(),
              temporary_dir()) as (prerelease_dep, transitive_dep, dep, dist_dir, cache_dir):

    for dist in (prerelease_dep, transitive_dep, dep):
      safe_copy(dist, os.path.join(dist_dir, os.path.basename(dist)))

    parser = configure_clp()

    options, reqs = parser.parse_args(args=[
      '--no-index',
      '--find-links', dist_dir,
      '--cache-dir', cache_dir,  # Avoid dangling {pex_root}.
      '--no-pre',
      'dep'
    ])
    assert not options.allow_prereleases

    with pytest.raises(SystemExit, message='Should have failed to resolve prerelease dep'):
      build_pex(reqs, options)

    # When we specify `--pre`, allow_prereleases is True
    options, reqs = parser.parse_args(args=[
      '--no-index',
      '--find-links', dist_dir,
      '--cache-dir', cache_dir,  # Avoid dangling {pex_root}.
      '--pre',
      'dep'
    ])
    assert options.allow_prereleases

    # Without a corresponding fix in pex.py, this test failed for a dependency requirement of
    # dep==1.2.3b1 from one package and just dep (any version accepted) from another package.
    # The failure was an exit from build_pex() with the message:
    #
    # Could not satisfy all requirements for dep==1.2.3b1:
    #     dep==1.2.3b1, dep
    #
    # With a correct behavior the assert line is reached and pex_builder object created.
    pex_builder = build_pex(reqs, options)
    assert pex_builder is not None
    assert len(pex_builder.info.distributions) == 3, 'Should have resolved deps'


def test_build_pex():
  with temporary_dir() as sandbox:
    pex_path = os.path.join(sandbox, 'pex')
    results = run_pex_command(['ansicolors==1.1.8', '--output-file', pex_path])
    results.assert_success()
    stdout, returncode = run_simple_pex(
      pex=pex_path,
      args=['-c', 'import colors; print(" ".join(colors.COLORS))']
    )
    assert 0 == returncode
    assert b'black red green yellow blue magenta cyan white' == stdout.strip()


def assert_run_pex(python=None, pex_args=None):
  pex_args = list(pex_args) if pex_args else []
  results = run_pex_command(
    python=python,
    args=pex_args + [
      'ansicolors==1.1.8', '--', '-c', 'import colors; print(" ".join(colors.COLORS))'
    ],
    quiet=True
  )
  results.assert_success()
  assert 'black red green yellow blue magenta cyan white' == results.output.strip()
  return results.error.splitlines()


def test_run_pex():
  incompatible_platforms_warning_msg = 'WARNING: attempting to run PEX with incompatible platforms!'

  assert incompatible_platforms_warning_msg not in assert_run_pex()
  assert incompatible_platforms_warning_msg not in assert_run_pex(pex_args=['--platform=current'])
  assert incompatible_platforms_warning_msg not in assert_run_pex(
    pex_args=['--platform={}'.format(Platform.current())]
  )

  py27 = ensure_python_interpreter(PY27)
  stderr_lines = assert_run_pex(python=py27, pex_args=['--platform=macosx-10.13-x86_64-cp-37-m'])
  assert incompatible_platforms_warning_msg in stderr_lines
