# Copyright 2018-2025 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import subprocess
import tempfile

from portage.const import PORTAGE_BIN_PATH
from portage.tests import TestCase


class TestEAPI7VerFuncs(TestCase):
    def _test_output(self, test_cases):
        """
        Test that commands in test_cases produce expected output.
        """
        with tempfile.NamedTemporaryFile("w") as test_script:
            test_script.write(f'source "{PORTAGE_BIN_PATH}"/version-functions.sh\n')
            for cmd, exp in test_cases:
                test_script.write(f"{cmd}\n")
            test_script.flush()

            s = subprocess.Popen(
                ["bash", test_script.name],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            sout, serr = s.communicate()
            self.assertEqual(s.returncode, 0)

            for test_case, result in zip(test_cases, sout.decode().splitlines()):
                cmd, exp = test_case
                self.assertEqual(result, exp, f"{cmd} -> {result}; expected: {exp}")

    def _test_return(self, test_cases):
        """
        Test that commands in test_cases give appropriate exit codes.
        """
        with tempfile.NamedTemporaryFile("w+") as test_script:
            test_script.write(f'source "{PORTAGE_BIN_PATH}"/version-functions.sh\n')
            for cmd, exp in test_cases:
                test_script.write(f"{cmd}; echo $?\n")
            test_script.flush()

            s = subprocess.Popen(
                ["bash", test_script.name],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            sout, serr = s.communicate()
            self.assertEqual(s.returncode, 0)

            for test_case, result in zip(test_cases, sout.decode().splitlines()):
                cmd, exp = test_case
                self.assertEqual(result, exp, f"{cmd} -> {result}; expected: {exp}")

    def _test_fail(self, test_cases):
        """
        Test that commands in test_cases fail.
        """

        for cmd in test_cases:
            test = f"""
source "{PORTAGE_BIN_PATH}"/version-functions.sh
die() {{ exit 1; }}
{cmd}"""

            s = subprocess.Popen(
                ["bash", "-c", test], stdout=subprocess.PIPE, stderr=subprocess.PIPE
            )
            sout, serr = s.communicate()
            self.assertEqual(
                s.returncode,
                1,
                f'"{cmd}" did not fail; output: {sout.decode()}; {serr.decode()})',
            )

    def test_ver_cut(self):
        test_cases = [
            # (command, output)
            ("ver_cut 1 1.2.3", "1"),
            ("ver_cut 1-1 1.2.3", "1"),
            ("ver_cut 1-2 1.2.3", "1.2"),
            ("ver_cut 2- 1.2.3", "2.3"),
            ("ver_cut 1- 1.2.3", "1.2.3"),
            ("ver_cut 3-4 1.2.3b_alpha4", "3b"),
            ("ver_cut 5 1.2.3b_alpha4", "alpha"),
            ("ver_cut 1-2 .1.2.3", "1.2"),
            ("ver_cut 0-2 .1.2.3", ".1.2"),
            ("ver_cut 2-3 1.2.3.", "2.3"),
            ("ver_cut 2- 1.2.3.", "2.3."),
            ("ver_cut 2-4 1.2.3.", "2.3."),
        ]
        self._test_output(test_cases)

    def test_ver_rs(self):
        test_cases = [
            # (command, output)
            ("ver_rs 1 - 1.2.3", "1-2.3"),
            ("ver_rs 2 - 1.2.3", "1.2-3"),
            ("ver_rs 1-2 - 1.2.3.4", "1-2-3.4"),
            ("ver_rs 2- - 1.2.3.4", "1.2-3-4"),
            ("ver_rs 2 . 1.2-3", "1.2.3"),
            ("ver_rs 3 . 1.2.3a", "1.2.3.a"),
            ("ver_rs 2-3 - 1.2_alpha4", "1.2-alpha-4"),
            ('ver_rs 3 - 2 "" 1.2.3b_alpha4', "1.23-b_alpha4"),
            ("ver_rs 3-5 _ 4-6 - a1b2c3d4e5", "a1b_2-c-3-d4e5"),
            ("ver_rs 1 - .1.2.3", ".1-2.3"),
            ("ver_rs 0 - .1.2.3", "-1.2.3"),
        ]
        self._test_output(test_cases)

    def test_truncated_range(self):
        test_cases = [
            # (command, output)
            ("ver_cut 0-2 1.2.3", "1.2"),
            ("ver_cut 2-5 1.2.3", "2.3"),
            ("ver_cut 4 1.2.3", ""),
            ("ver_cut 0 1.2.3", ""),
            ("ver_cut 4- 1.2.3", ""),
            ("ver_rs 0 - 1.2.3", "1.2.3"),
            ("ver_rs 3 . 1.2.3", "1.2.3"),
            ("ver_rs 3- . 1.2.3", "1.2.3"),
            ("ver_rs 3-5 . 1.2.3", "1.2.3"),
        ]
        self._test_output(test_cases)

    def test_invalid_range(self):
        test_cases = [
            "ver_cut foo 1.2.3",
            "ver_rs -3 _ a1b2c3d4e5",
            "ver_rs 5-3 _ a1b2c3d4e5",
        ]
        self._test_fail(test_cases)

    def test_ver_test(self):
        test_cases = [
            # Tests from Portage's test_vercmp.py
            ("ver_test 6.0 -gt 5.0", "0"),
            ("ver_test 5.0 -gt 5", "0"),
            ("ver_test 1.0-r1 -gt 1.0-r0", "0"),
            ("ver_test 999999999999999999 -gt 999999999999999998", "0"),  # 18 digits
            ("ver_test 1.0.0 -gt 1.0", "0"),
            ("ver_test 1.0.0 -gt 1.0b", "0"),
            ("ver_test 1b -gt 1", "0"),
            ("ver_test 1b_p1 -gt 1_p1", "0"),
            ("ver_test 1.1b -gt 1.1", "0"),
            ("ver_test 12.2.5 -gt 12.2b", "0"),
            ("ver_test 4.0 -lt 5.0", "0"),
            ("ver_test 5 -lt 5.0", "0"),
            ("ver_test 1.0_pre2 -lt 1.0_p2", "0"),
            ("ver_test 1.0_alpha2 -lt 1.0_p2", "0"),
            ("ver_test 1.0_alpha1 -lt 1.0_beta1", "0"),
            ("ver_test 1.0_beta3 -lt 1.0_rc3", "0"),
            ("ver_test 1.001000000000000001 -lt 1.001000000000000002", "0"),
            ("ver_test 1.00100000000 -lt 1.001000000000000001", "0"),
            ("ver_test 999999999999999998 -lt 999999999999999999", "0"),
            ("ver_test 1.01 -lt 1.1", "0"),
            ("ver_test 1.0-r0 -lt 1.0-r1", "0"),
            ("ver_test 1.0 -lt 1.0-r1", "0"),
            ("ver_test 1.0 -lt 1.0.0", "0"),
            ("ver_test 1.0b -lt 1.0.0", "0"),
            ("ver_test 1_p1 -lt 1b_p1", "0"),
            ("ver_test 1 -lt 1b", "0"),
            ("ver_test 1.1 -lt 1.1b", "0"),
            ("ver_test 12.2b -lt 12.2.5", "0"),
            ("ver_test 4.0 -eq 4.0", "0"),
            ("ver_test 1.0 -eq 1.0", "0"),
            ("ver_test 1.0-r0 -eq 1.0", "0"),
            ("ver_test 1.0 -eq 1.0-r0", "0"),
            ("ver_test 1.0-r0 -eq 1.0-r0", "0"),
            ("ver_test 1.0-r1 -eq 1.0-r1", "0"),
            ("ver_test 1 -eq 2", "1"),
            ("ver_test 1.0_alpha -eq 1.0_pre", "1"),
            ("ver_test 1.0_beta -eq 1.0_alpha", "1"),
            ("ver_test 1 -eq 0.0", "1"),
            ("ver_test 1.0-r0 -eq 1.0-r1", "1"),
            ("ver_test 1.0-r1 -eq 1.0-r0", "1"),
            ("ver_test 1.0 -eq 1.0-r1", "1"),
            ("ver_test 1.0-r1 -eq 1.0", "1"),
            ("ver_test 1.0 -eq 1.0.0", "1"),
            ("ver_test 1_p1 -eq 1b_p1", "1"),
            ("ver_test 1b -eq 1", "1"),
            ("ver_test 1.1b -eq 1.1", "1"),
            ("ver_test 12.2b -eq 12.2", "1"),
            # A subset of tests from Paludis
            ("ver_test 1.0_alpha -gt 1_alpha", "0"),
            ("ver_test 1.0_alpha -gt 1", "0"),
            ("ver_test 1.0_alpha -lt 1.0", "0"),
            ("ver_test 1.2.0.0_alpha7-r4 -gt 1.2_alpha7-r4", "0"),
            ("ver_test 0001 -eq 1", "0"),
            ("ver_test 01 -eq 001", "0"),
            ("ver_test 0001.1 -eq 1.1", "0"),
            ("ver_test 01.01 -eq 1.01", "0"),
            ("ver_test 1.010 -eq 1.01", "0"),
            ("ver_test 1.00 -eq 1.0", "0"),
            ("ver_test 1.0100 -eq 1.010", "0"),
            ("ver_test 1-r00 -eq 1-r0", "0"),
            # Additional tests
            ("ver_test 0_rc99 -lt 0", "0"),
            ("ver_test 011 -eq 11", "0"),
            ("ver_test 019 -eq 19", "0"),
            ("ver_test 1.2 -eq 001.2", "0"),
            ("ver_test 1.2 -gt 1.02", "0"),
            ("ver_test 1.2a -lt 1.2b", "0"),
            ("ver_test 1.2_pre1 -gt 1.2_pre1_beta2", "0"),
            ("ver_test 1.2_pre1 -lt 1.2_pre1_p2", "0"),
            ("ver_test 1.00 -lt 1.0.0", "0"),
            ("ver_test 1.010 -eq 1.01", "0"),
            ("ver_test 1.01 -lt 1.1", "0"),
            ("ver_test 1.2_pre08-r09 -eq 1.2_pre8-r9", "0"),
            ("ver_test 0 -lt 576460752303423488", "0"),  # 2**59
            ("ver_test 0 -lt 9223372036854775808", "0"),  # 2**63
        ]
        self._test_return(test_cases)

    def test_invalid_test(self):
        test_cases = [
            # Bad number or ordering of arguments
            "ver_test 1",
            "ver_test 1 -lt 2 3",
            "ver_test -lt 1 2",
            # Bad operators
            'ver_test 1 "<" 2',
            "ver_test 1 lt 2",
            "ver_test 1 -foo 2",
            # Malformed versions
            'ver_test "" -ne 1',
            "ver_test 1. -ne 1",
            "ver_test 1ab -ne 1",
            "ver_test b -ne 1",
            "ver_test 1-r1_pre -ne 1",
            "ver_test 1-pre1 -ne 1",
            "ver_test 1_foo -ne 1",
            "ver_test 1_pre1.1 -ne 1",
            "ver_test 1-r1.0 -ne 1",
            "ver_test cvs.9999 -ne 9999",
        ]
        self._test_fail(test_cases)
