# Copyright (C) 2012 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause


from copy import copy
from random import shuffle
import unittest

from conda.exceptions import InvalidVersionSpec
from conda.models.version import VersionOrder, VersionSpec, normalized_version, ver_eval
import pytest


class TestVersionSpec(unittest.TestCase):

    def test_version_order(self):
        versions = [
           ("0.4",         [[0], [0], [4]]),
           ("0.4.0",      [[0], [0], [4], [0]]),
           ("0.4.1a.vc11",[[0], [0], [4], [1, 'a'],[0, 'vc', 11]]),
           ("0.4.1.rc",   [[0], [0], [4], [1], [0, 'rc']]),
           ("0.4.1.vc11", [[0], [0], [4], [1],[0, 'vc', 11]]),
           ("0.4.1",      [[0], [0], [4], [1]]),
           ("0.5*",       [[0], [0], [5, '*']]),
           ("0.5a1",      [[0], [0], [5, 'a', 1]]),
           ("0.5b3",      [[0], [0], [5, 'b', 3]]),
           ("0.5C1",      [[0], [0], [5, 'c', 1]]),
           ("0.5z",       [[0], [0], [5, 'z']]),
           ("0.5za",      [[0], [0], [5, 'za']]),
           ("0.5",        [[0], [0], [5]]),
           ("0.5_5",      [[0], [0], [5], [5]]),
           ("0.5-5",      [[0], [0], [5], [5]]),
           ("0.9.6",      [[0], [0], [9], [6]]),
           ("0.960923",   [[0], [0], [960923]]),
           ("1.0",        [[0], [1], [0]]),
           ("1.0.4a3",    [[0], [1], [0], [4, 'a', 3]]),
           ("1.0.4b1",    [[0], [1], [0], [4, 'b', 1]]),
           ("1.0.4",      [[0], [1], [0], [4]]),
           ("1.1dev1",    [[0], [1], [1, 'DEV', 1]]),
           ("1.1_",       [[0], [1], [1, '_']]),
           ("1.1a1",      [[0], [1], [1, 'a', 1]]),
           ("1.1.dev1",   [[0], [1], [1], [0, 'DEV', 1]]),
           ("1.1.a1",     [[0], [1], [1], [0, 'a', 1]]),
           ("1.1",        [[0], [1], [1]]),
           ("1.1.post1",  [[0], [1], [1], [0, float('inf'), 1]]),
           ("1.1.1dev1",  [[0], [1], [1], [1, 'DEV', 1]]),
           ("1.1.1rc1",   [[0], [1], [1], [1, 'rc', 1]]),
           ("1.1.1",      [[0], [1], [1], [1]]),
           ("1.1.1post1", [[0], [1], [1], [1, float('inf'), 1]]),
           ("1.1post1",   [[0], [1], [1, float('inf'), 1]]),
           ("2g6",        [[0], [2, 'g', 6]]),
           ("2.0b1pr0",   [[0], [2], [0, 'b', 1, 'pr', 0]]),
           ("2.2be.ta29", [[0], [2], [2, 'be'], [0, 'ta', 29]]),
           ("2.2be5ta29", [[0], [2], [2, 'be', 5, 'ta', 29]]),
           ("2.2beta29",  [[0], [2], [2, 'beta', 29]]),
           ("2.2.0.1",    [[0], [2], [2],[0],[1]]),
           ("3.1.1.6",    [[0], [3], [1], [1], [6]]),
           ("3.2.p.r0",   [[0], [3], [2], [0, 'p'], [0, 'r', 0]]),
           ("3.2.pr0",    [[0], [3], [2], [0, 'pr', 0]]),
           ("3.2.pr.1",   [[0], [3], [2], [0, 'pr'], [1]]),
           ("5.5.kw",     [[0], [5], [5], [0, 'kw']]),
           ("11g",        [[0], [11, 'g']]),
           ("14.3.1",     [[0], [14], [3], [1]]),
           ("14.3.1.post26.g9d75ca2",
                                        [[0],[14],[3],[1],[0,float('inf'),26],[0,'g',9,'d',75,'ca',2]]),
           ("1996.07.12", [[0], [1996], [7], [12]]),
           ("1!0.4.1",    [[1], [0], [4], [1]]),
           ("1!3.1.1.6",  [[1], [3], [1], [1], [6]]),
           ("2!0.4.1",    [[2], [0], [4], [1]]),
        ]

        # check parser
        versions = [(v, VersionOrder(v), expected) for v, expected in versions]
        for s, v, expected in versions:
            assert VersionOrder(v) is v
            assert str(v) == s.lower().replace('-', '_')
            self.assertEqual(v.version, expected)
        self.assertEqual(VersionOrder("0.4.1.rc"), VersionOrder("  0.4.1.RC  "))
        self.assertEqual(normalized_version("  0.4.1.RC  "), VersionOrder("0.4.1.rc"))
        for ver in ("", "", "  ", "3.5&1", "5.5++", "5.5..mw", "!", "a!1.0", "a!b!1.0"):
            self.assertRaises(ValueError, VersionOrder, ver)

        # check __eq__
        self.assertEqual(VersionOrder("  0.4.rc  "), VersionOrder("0.4.RC"))
        self.assertEqual(VersionOrder("0.4"), VersionOrder("0.4.0"))
        self.assertNotEqual(VersionOrder("0.4"), VersionOrder("0.4.1"))
        self.assertEqual(VersionOrder("0.4.a1"), VersionOrder("0.4.0a1"))
        self.assertNotEqual(VersionOrder("0.4.a1"), VersionOrder("0.4.1a1"))

        # check __lt__
        self.assertEqual(sorted(versions, key=lambda x: x[1]), versions)

        # check startswith
        self.assertTrue(VersionOrder("0.4.1").startswith(VersionOrder("0")))
        self.assertTrue(VersionOrder("0.4.1").startswith(VersionOrder("0.4")))
        self.assertTrue(VersionOrder("0.4.1p1").startswith(VersionOrder("0.4")))
        self.assertTrue(VersionOrder("0.4.1p1").startswith(VersionOrder("0.4.1p")))
        self.assertFalse(VersionOrder("0.4.1p1").startswith(VersionOrder("0.4.1q1")))
        self.assertFalse(VersionOrder("0.4").startswith(VersionOrder("0.4.1")))
        self.assertTrue(VersionOrder("0.4.1+1.3").startswith(VersionOrder("0.4.1")))
        self.assertTrue(VersionOrder("0.4.1+1.3").startswith(VersionOrder("0.4.1+1")))
        self.assertFalse(VersionOrder("0.4.1").startswith(VersionOrder("0.4.1+1.3")))
        self.assertFalse(VersionOrder("0.4.1+1").startswith(VersionOrder("0.4.1+1.3")))

    def test_openssl_convention(self):
        openssl = [VersionOrder(k) for k in (
            '1.0.1dev',
            '1.0.1_',  # <- this
            '1.0.1a',
            '1.0.1b',
            '1.0.1c',
            '1.0.1d',
            '1.0.1r',
            '1.0.1rc',
            '1.0.1rc1',
            '1.0.1rc2',
            '1.0.1s',
            '1.0.1',  # <- compared to this
            '1.0.1post.a',
            '1.0.1post.b',
            '1.0.1post.z',
            '1.0.1post.za',
            '1.0.2',
        )]
        shuffled = copy(openssl)
        shuffle(shuffled)
        assert sorted(shuffled) == openssl

    def test_pep440(self):
        # this list must be in sorted order (slightly modified from the PEP 440 test suite
        # https://github.com/pypa/packaging/blob/master/tests/test_version.py)
        VERSIONS = [
            # Implicit epoch of 0
            "1.0a1", "1.0a2.dev456", "1.0a12.dev456", "1.0a12",
            "1.0b1.dev456", "1.0b2", "1.0b2.post345.dev456", "1.0b2.post345",
            "1.0c1.dev456", "1.0c1", "1.0c3", "1.0rc2", "1.0.dev456", "1.0",
            "1.0.post456.dev34", "1.0.post456", "1.1.dev1",
            "1.2.r32+123456", "1.2.rev33+123456",
            "1.2+abc", "1.2+abc123def", "1.2+abc123",
            "1.2+123abc", "1.2+123abc456", "1.2+1234.abc", "1.2+123456",

            # Explicit epoch of 1
            "1!1.0a1", "1!1.0a2.dev456", "1!1.0a12.dev456", "1!1.0a12",
            "1!1.0b1.dev456", "1!1.0b2", "1!1.0b2.post345.dev456", "1!1.0b2.post345",
            "1!1.0c1.dev456", "1!1.0c1", "1!1.0c3", "1!1.0rc2", "1!1.0.dev456", "1!1.0",
            "1!1.0.post456.dev34", "1!1.0.post456", "1!1.1.dev1",
            "1!1.2.r32+123456", "1!1.2.rev33+123456",
            "1!1.2+abc", "1!1.2+abc123def", "1!1.2+abc123",
            "1!1.2+123abc", "1!1.2+123abc456", "1!1.2+1234.abc", "1!1.2+123456",
        ]

        version = [VersionOrder(v) for v in VERSIONS]

        self.assertEqual(version, sorted(version))

    def test_hexrd(self):
        VERSIONS = ['0.3.0.dev', '0.3.3']
        vos = [VersionOrder(v) for v in VERSIONS]
        self.assertEqual(sorted(vos), vos)

    def test_ver_eval(self):
        self.assertEqual(ver_eval('1.7.0', '==1.7'), True)
        self.assertEqual(ver_eval('1.7.0', '<=1.7'), True)
        self.assertEqual(ver_eval('1.7.0', '<1.7'), False)
        self.assertEqual(ver_eval('1.7.0', '>=1.7'), True)
        self.assertEqual(ver_eval('1.7.0', '>1.7'), False)
        self.assertEqual(ver_eval('1.6.7', '>=1.7'), False)
        self.assertEqual(ver_eval('2013a', '>2013b'), False)
        self.assertEqual(ver_eval('2013k', '>2013b'), True)
        self.assertEqual(ver_eval('3.0.0', '>2013b'), False)
        self.assertEqual(ver_eval('1.0.0', '>1.0.0a'), True)
        self.assertEqual(ver_eval('1.0.0', '>1.0.0*'), True)
        self.assertEqual(ver_eval('1.0', '1.0*'), True)
        self.assertEqual(ver_eval('1.0.0', '1.0*'), True)
        self.assertEqual(ver_eval('1.0', '1.0.0*'), True)
        self.assertEqual(ver_eval('1.0.1', '1.0.0*'), False)
        self.assertEqual(ver_eval('2013a', '2013a*'), True)
        self.assertEqual(ver_eval('2013a', '2013b*'), False)
        self.assertEqual(ver_eval('2013ab', '2013a*'), True)
        self.assertEqual(ver_eval('1.3.4', '1.2.4*'), False)
        self.assertEqual(ver_eval('1.2.3+4.5.6', '1.2.3*'), True)
        self.assertEqual(ver_eval('1.2.3+4.5.6', '1.2.3+4*'), True)
        self.assertEqual(ver_eval('1.2.3+4.5.6', '1.2.3+5*'), False)
        self.assertEqual(ver_eval('1.2.3+4.5.6', '1.2.4+5*'), False)

    def test_ver_eval_errors(self):
        self.assertRaises(InvalidVersionSpec, ver_eval, '3.0.0', '><2.4.5')
        self.assertRaises(InvalidVersionSpec, ver_eval, '3.0.0', '!!2.4.5')
        self.assertRaises(InvalidVersionSpec, ver_eval, '3.0.0', '!')

    def test_version_spec_1(self):
        v1 = VersionSpec('1.7.1')
        v2 = VersionSpec('1.7.1*')
        v3 = VersionSpec('1.7.1')
        self.assertTrue(v1.is_exact())
        self.assertFalse(v2.is_exact())
        self.assertTrue(v1 == v3)
        self.assertFalse(v1 != v3)
        self.assertTrue(v1 != v2)
        self.assertFalse(v1 == v2)
        self.assertTrue(v1 != 1.0)
        self.assertFalse(v1 == 1.0)
        self.assertEqual(hash(v1), hash(v3))
        self.assertNotEqual(hash(v1), hash(v2))

    def test_version_spec_2(self):
        v1 = VersionSpec('( (1.5|((1.6|1.7), 1.8), 1.9 |2.0))|2.1')
        self.assertEqual(v1.spec, '1.5|1.6|1.7,1.8,1.9|2.0|2.1')
        self.assertRaises(InvalidVersionSpec, VersionSpec, '(1.5')
        self.assertRaises(InvalidVersionSpec, VersionSpec, '1.5)')
        self.assertRaises(InvalidVersionSpec, VersionSpec, '1.5||1.6')
        self.assertRaises(InvalidVersionSpec, VersionSpec, '^1.5')

    def test_version_spec_3(self):
        v1 = VersionSpec('1.7.1*')
        v2 = VersionSpec('1.7.1.*')
        self.assertFalse(v1.is_exact())
        self.assertFalse(v2.is_exact())
        self.assertTrue(v1 == v2)
        self.assertFalse(v1 != v2)
        self.assertEqual(hash(v1), hash(v2))

    def test_version_spec_4(self):
        v1 = VersionSpec('1.7.1*,1.8.1*')
        v2 = VersionSpec('1.7.1.*,1.8.1.*')
        v3 = VersionSpec('1.7.1*,1.8.1.*')
        assert v1.is_exact() is False
        assert v2.is_exact() is False
        assert v1 == v2 == v3
        assert not v1 != v2
        assert hash(v1) == hash(v2) == hash(v3)

    def test_match(self):
        for vspec, res in (
            ('1.7.*', True),   ('1.7.1', True),    ('1.7.0', False),
            ('1.7', False),   ('1.5.*', False),    ('>=1.5', True),
            ('!=1.5', True),  ('!=1.7.1', False), ('==1.7.1', True),
            ('==1.7', False), ('==1.7.2', False), ('==1.7.1.0', True),
            ('1.7.*|1.8.*', True),
            # ('1.8/*|1.9.*', False),  what was this supposed to be?
            ('>1.7,<1.8', True), ('>1.7.1,<1.8', False),
            ('^1.7.1$', True), (r'^1\.7\.1$', True), (r'^1\.7\.[0-9]+$', True),
            (r'^1\.8.*$', False), (r'^1\.[5-8]\.1$', True), (r'^[^1].*$', False),
            (r'^[0-9+]+\.[0-9+]+\.[0-9]+$', True), ('^$', False),
            ('^.*$', True), ('1.7.*|^0.*$', True), ('1.6.*|^0.*$', False),
            ('1.6.*|^0.*$|1.7.1', True), ('^0.*$|1.7.1', True),
            (r'1.6.*|^.*\.7\.1$|0.7.1', True), ('*', True), ('1.*.1', True),
            ('1.5.*|>1.7,<1.8', True), ('1.5.*|>1.7,<1.7.1', False),
        ):
            m = VersionSpec(vspec)
            assert VersionSpec(m) is m
            assert str(m) == vspec
            assert repr(m) == "VersionSpec('%s')" % vspec
            assert m.match('1.7.1') == res, vspec

    def test_local_identifier(self):
        """The separator for the local identifier should be either `.` or `+`"""
        # a valid versionstr should match itself
        versions = (
            '1.7.0'
            '1.7.0.post123'
            '1.7.0.post123.gabcdef9',
            '1.7.0.post123+gabcdef9',
        )
        for version in versions:
            m = VersionSpec(version)
            self.assertTrue(m.match(version))

    def test_not_eq_star(self):
        assert VersionSpec("=3.3").match("3.3.1")
        assert VersionSpec("=3.3").match("3.3")
        assert not VersionSpec("=3.3").match("3.4")

        assert VersionSpec("3.3.*").match("3.3.1")
        assert VersionSpec("3.3.*").match("3.3")
        assert not VersionSpec("3.3.*").match("3.4")

        assert VersionSpec("=3.3.*").match("3.3.1")
        assert VersionSpec("=3.3.*").match("3.3")
        assert not VersionSpec("=3.3.*").match("3.4")

        assert not VersionSpec("!=3.3.*").match("3.3.1")
        assert VersionSpec("!=3.3.*").match("3.4")
        assert VersionSpec("!=3.3.*").match("3.4.1")

        assert VersionSpec("!=3.3").match("3.3.1")
        assert not VersionSpec("!=3.3").match("3.3.0.0")
        assert not VersionSpec("!=3.3.*").match("3.3.0.0")

    def test_compound_versions(self):
        vs = VersionSpec('>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*')
        assert not vs.match('2.6.8')
        assert vs.match('2.7.2')
        assert not vs.match('3.3')
        assert not vs.match('3.3.4')
        assert vs.match('3.4')
        assert vs.match('3.4a')

    def test_invalid_version_specs(self):
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("~")
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("^")
        # Test for previously mishandled uses of local version separator '+'
        # uncovered by fuzzing efforts
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("1.2+")
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("+1.2")
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("+1.2+")
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("++")
        # Fuzzer-identified crashers:
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("c +, 0/|0 *")

        # Weird crashing versions intended to test unhandled edge cases
        # in the "treeify" process
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("a[version=)|(")
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("a=)(=b")

        # Test for mishandling of '=='  and '=' without a version number
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("==")
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("=")
        # Additional tests based on the above
        with pytest.raises(InvalidVersionSpec):
            VersionSpec(">=")
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("<=")

    def test_compatible_release_versions(self):
        assert VersionSpec("~=1.10").match("1.11.0")
        assert not VersionSpec("~=1.10.0").match("1.11.0")

        assert not VersionSpec("~=3.3.2").match("3.4.0")
        assert not VersionSpec("~=3.3.2").match("3.3.1")
        assert VersionSpec("~=3.3.2").match("3.3.2.0")
        assert VersionSpec("~=3.3.2").match("3.3.3")

        assert VersionSpec("~=3.3.2|==2.2").match("2.2.0")
        assert VersionSpec("~=3.3.2|==2.2").match("3.3.3")
        assert not VersionSpec("~=3.3.2|==2.2").match("2.2.1")

        with pytest.raises(InvalidVersionSpec):
            VersionSpec("~=3.3.2.*")

    def test_pep_440_arbitrary_equality_operator(self):
        # We're going to leave the not implemented for now.
        with pytest.raises(InvalidVersionSpec):
            VersionSpec("===3.3.2")
