import copy
import time
from collections import OrderedDict

import pytest
from unittest.mock import patch

from conan.test.utils.env import environment_update
from conan.internal.errors import RecipeNotFoundException
from conan.api.model import RecipeReference
from conans.server.revision_list import RevisionList
from conan.test.utils.tools import TestServer, GenConanfile, TestClient
from conan.internal.util.files import load


def _create(c_v2, ref, conanfile=None, args=None, assert_error=False):
    conanfile = conanfile or GenConanfile()
    c_v2.save({"conanfile.py": str(conanfile)})
    r = ref
    c_v2.run(f"create . --name {r.name} --version {r.version} "
             f"--user {r.user} --channel {r.channel} {args or ''}", assert_error=assert_error)
    if not assert_error:
        pref = c_v2.created_package_reference(str(ref))
        return pref


@pytest.mark.artifactory_ready
class TestInstallingPackagesWithRevisions:

    @pytest.fixture(autouse=True)
    def setup(self):
        self.server = TestServer()
        self.server2 = TestServer()
        self.servers = OrderedDict([("default", self.server),
                                    ("remote2", self.server2)])
        self.c_v2 = TestClient(servers=self.servers, inputs=2*["admin", "password"])
        self.ref = RecipeReference.loads("lib/1.0@conan/testing")

    def recipe_revision(self, ref):
        tmp = copy.copy(ref)
        tmp.revision = None
        latest_rrev = self.c_v2.cache.get_latest_recipe_revision(tmp)
        return latest_rrev.revision

    def package_revision(self, pref):
        tmp = copy.copy(pref)
        tmp.revision = None
        latest_prev = self.c_v2.cache.get_latest_package_revision(tmp)
        return latest_prev.revision

    def test_install_binary_iterating_remotes_same_rrev(self):
        """We have two servers (remote1 and remote2), first with a recipe but the
        second one with a PREV of the binary.
        If a client installs without specifying -r remote1, it will iterate remote2 also"""
        conanfile = GenConanfile().with_package_file("file.txt", env_var="MY_VAR")
        with environment_update({"MY_VAR": "1"}):
            pref = _create(self.c_v2, self.ref, conanfile=conanfile)
        the_time = time.time()
        with patch.object(RevisionList, '_now', return_value=the_time):
            self.c_v2.run(f"upload {self.ref} -r=default -c")
        self.c_v2.run("remove {}#*:{} -c -r default".format(self.ref, pref.package_id))
        # Same RREV, different PREV
        with environment_update({"MY_VAR": "2"}):
            pref2 = _create(self.c_v2, self.ref, conanfile=conanfile)

        the_time = the_time + 10.0
        with patch.object(RevisionList, '_now', return_value=the_time):
            self.c_v2.run(f"upload {self.ref} -r=remote2 -c")
        self.c_v2.remove_all()

        assert pref.ref.revision == pref2.ref.revision

        self.c_v2.run("install --requires={}".format(self.ref))
        self.c_v2.assert_listed_require({str(self.ref): "Downloaded (default)"})
        assert "Retrieving package {} from remote 'remote2'".format(pref.package_id) in self.c_v2.out

    def test_diamond_revisions_conflict(self):
        """ If we have a diamond because of pinned conflicting revisions in the requirements,
        it gives an error"""

        # Two revisions of "lib1" to the server
        lib1 = RecipeReference.loads("lib1/1.0@conan/stable")
        lib1_pref = _create(self.c_v2, lib1)
        self.c_v2.run(f"upload {lib1} -r=default -c")
        lib1b_pref = _create(self.c_v2, lib1, conanfile=GenConanfile().with_build_msg("Rev2"))
        self.c_v2.run(f"upload {lib1} -r=default -c")

        # Lib2 depending of lib1
        self.c_v2.remove_all()
        lib2 = RecipeReference.loads("lib2/1.0@conan/stable")
        _create(self.c_v2, lib2, conanfile=GenConanfile().with_requirement(lib1_pref.ref))
        self.c_v2.run(f"upload {lib2} -r=default -c")

        # Lib3 depending of lib1b
        self.c_v2.remove_all()
        lib3 = RecipeReference.loads("lib3/1.0@conan/stable")
        _create(self.c_v2, lib3, conanfile=GenConanfile().with_requirement(lib1b_pref.ref))
        self.c_v2.run(f"upload {lib3} -r=default -c")

        # Project depending on both lib3 and lib2
        self.c_v2.remove_all()
        project = RecipeReference.loads("project/1.0@conan/stable")
        _create(self.c_v2, project,
                conanfile=GenConanfile().with_requirement(lib2).with_requirement(lib3),
                assert_error=True)
        assert "ERROR: Version conflict" in self.c_v2.out
        # assert "Different revisions of {} has been requested".format(lib1), self.c_v2.out)

    def test_alias_to_a_rrev(self):
        """ If an alias points to a RREV, it resolved that RREV and no other"""

        # Upload one revision
        pref = _create(self.c_v2, self.ref)
        self.c_v2.run(f"upload {self.ref} -r=default -c")

        # Upload other revision
        _create(self.c_v2, self.ref, conanfile=GenConanfile().with_build_msg("Build Rev 2"))
        self.c_v2.run(f"upload {self.ref} -r=default -c")
        self.c_v2.remove_all()

        # Create an alias to the first revision
        self.c_v2.alias("lib/latest@conan/stable", repr(pref.ref))
        alias_ref = RecipeReference.loads("lib/latest@conan/stable")
        exported = load(self.c_v2.get_latest_ref_layout(alias_ref).conanfile())
        assert 'alias = "{}"'.format(repr(pref.ref)) in exported

        self.c_v2.run(f"upload lib/latest@conan/stable -r=default -c")
        self.c_v2.remove_all()

        self.c_v2.run("install --requires=lib/(latest)@conan/stable")
        # Shouldn't be packages in the cache
        assert "doesn't belong to the installed recipe revision" not in self.c_v2.out

        # Read current revision
        assert pref.ref.revision == self.recipe_revision(self.ref)

    def test_revision_metadata_update_on_install(self):
        """If a clean v2 client installs a RREV/PREV from a server, it get
        the revision from upstream"""
        # Upload with v2
        pref = _create(self.c_v2, self.ref)
        self.c_v2.run(f"upload {self.ref} -r=default -c")

        # Remove all from c_v2 local
        self.c_v2.remove_all()
        assert len(self.c_v2.cache.get_recipe_revisions(self.ref)) == 0

        self.c_v2.run("install --requires={}".format(self.ref))
        local_rev = self.recipe_revision(self.ref)
        local_prev = self.package_revision(pref)
        assert local_rev == pref.ref.revision
        assert local_prev == pref.revision

    def test_revision_update_on_package_update(self):
        """
        A client v2 upload RREV with PREV1
        Another client v2 upload the same RREV with PREV2
        The first client can upgrade from the remote, only
        in the package, because the recipe is the same and it is not updated"""
        client = TestClient(servers={"default": self.server}, inputs=["admin", "password"])
        client2 = TestClient(servers={"default": self.server}, inputs=["admin", "password"])

        conanfile = GenConanfile("pkg", "0.1").with_package_file("file", env_var="MY_VAR")
        client.save({"conanfile.py": conanfile})
        with environment_update({"MY_VAR": "1"}):
            client.run("create .")
            pref = client.created_package_reference("pkg/0.1")

        time.sleep(1)

        with patch.object(RevisionList, '_now', return_value=time.time()):
            client.run(f"upload * -r=default -c")

        client2.save({"conanfile.py": conanfile})
        with environment_update({"MY_VAR": "2"}):
            client2.run("create .")
            pref2 = client2.created_package_reference("pkg/0.1")

        with patch.object(RevisionList, '_now', return_value=time.time() + 20.0):
            client2.run(f"upload * -r=default -c")

        prev1_time_remote = self.server.package_revision_time(pref)
        prev2_time_remote = self.server.package_revision_time(pref2)
        assert prev1_time_remote != prev2_time_remote  # Two package revisions

        client.run("install --requires=pkg/0.1 --update")
        client.assert_listed_require({"pkg/0.1": "Cache (Updated date) (default)"})
        assert "Retrieving package {}".format(pref.package_id) in client.out

    def test_revision_mismatch_packages_in_local(self):
        """Test adapted for the new cache: we create a revision but we export again a  recipe
        to create a new revision, then we won't have a package for the latest recipe revision
        of the cache.
        TODO: cache2.0 check this case"""
        client = self.c_v2
        pref = _create(self.c_v2, self.ref)
        ref2 = client.export(self.ref, conanfile=GenConanfile().with_build_msg("REV2"))
        # Now we have two RREVs and a PREV corresponding to the first one
        sot1 = copy.copy(pref.ref)
        sot1.revision = None
        sot2 = copy.copy(ref2)
        sot2.revision = None
        assert sot1 == sot2
        assert pref.ref.revision != ref2.revision

        # Now we try to install the self.ref, the binary is missing when using revisions
        command = "install --requires={}".format(self.ref)
        client.run(command, assert_error=True)
        assert "ERROR: Missing prebuilt package for '{}'".format(self.ref) in client.out

    def test_revision_install_explicit_mismatch_rrev(self):
        # If we have a recipe in local, but we request to install a different one with RREV
        # It fail and won't look the remotes unless --update
        client = self.c_v2
        ref = client.export(self.ref)
        command = "install --requires={}#fakerevision".format(ref)
        client.run(command, assert_error=True)
        assert "Unable to find '{}#fakerevision' in remotes".format(ref) in client.out
        command = "install --requires={}#fakerevision --update".format(ref)
        client.run(command, assert_error=True)
        assert "Unable to find '{}#fakerevision' in remotes".format(ref) in client.out

        # Now create a new revision with other client and upload it, we will request it
        new_client = TestClient(servers=self.servers, inputs=["admin", "password"])
        new_client.save({"conanfile.py": GenConanfile("lib", "1.0").with_build_msg("Rev2")})
        new_client.run("create . --user=conan --channel=testing")
        pref = new_client.created_package_reference(self.ref)
        new_client.run(f"upload * -r=default -c")

        # Repeat the install --update pointing to the new reference
        client.run("install --requires={} --update".format(repr(pref.ref)))
        client.assert_listed_require({str(self.ref): "Downloaded (default)"})

    def test_revision_mismatch_packages_remote(self):
        """If we have a recipe that doesn't match a remote recipe:
         It is not resolved in the remote."""
        _create(self.c_v2, self.ref)
        self.c_v2.run(f"upload {self.ref} -r=default -c")

        client = self.c_v2
        client.remove_all()
        client.export(self.ref, conanfile=GenConanfile().with_build_msg("REV2"))
        command = "install --requires={}".format(self.ref)

        client.run(command, assert_error=True)
        assert "Can't find a '{}' package".format(self.ref) in client.out

    def test_revision_build_requires(self):
        conanfile = GenConanfile()

        refs = []
        for _ in range(1, 4):  # create different revisions
            conanfile.with_build_msg("any change to get another rrev")
            pref = _create(self.c_v2, self.ref, conanfile=conanfile)
            self.c_v2.run(f"upload {pref.ref} -r=default -c")
            refs.append(pref.ref)
            assert refs.count(pref.ref) == 1  # make sure that all revisions are different

        client = self.c_v2  # revisions enabled
        client.remove_all()

        for ref in refs:
            command = "install --update --tool-require={}".format(repr(ref))
            client.run(command)
            assert "Downloaded recipe revision {}".format(ref.revision) in client.out


class TestRemoveWithRevisions:

    @pytest.fixture(autouse=True)
    def setup(self):
        self.server = TestServer()
        self.c_v2 = TestClient(servers={"default": self.server}, inputs=["admin", "password"])
        self.ref = RecipeReference.loads("lib/1.0@conan/testing")

    def test_remove_local_recipe(self):
        """Locally: When I remove a recipe with RREV only if the local revision matches is removed"""
        client = self.c_v2

        # If I remove the ref, the revision is gone, of course
        ref1 = client.export(self.ref)
        ref1.revision = None
        client.run("remove {} -c".format(repr(ref1)))
        assert not client.recipe_exists(self.ref)

        # If I remove a ref with a wrong revision, the revision is not removed
        ref1 = client.export(self.ref)
        fakeref = copy.copy(ref1)
        fakeref.revision = "fakerev"
        full_ref = repr(fakeref)
        client.run("remove {} -c".format(repr(fakeref)), assert_error=True)
        assert f"ERROR: Recipe revision '{full_ref}' not found" in client.out
        assert client.recipe_exists(self.ref)

    def test_remove_local_package(self):
        """Locally:
            When I remove a recipe without RREV, the package is removed.
            When I remove a recipe with RREV only if the local revision matches is removed
            When I remove a package with PREV and not RREV it raises an error
            When I remove a package with RREV and PREV only when both matches is removed"""
        client = self.c_v2

        # If I remove the ref without RREV, the packages are also removed
        pref1 = _create(self.c_v2, self.ref)
        tmp = copy.copy(pref1.ref)
        tmp.revision = None
        client.run("remove {} -c".format(repr(tmp)))
        assert not client.package_exists(pref1)

        # If I remove the ref with fake RREV, the packages are not removed
        pref1 = _create(self.c_v2, self.ref)
        fakeref = copy.copy(pref1.ref)
        fakeref.revision = "fakerev"
        str_ref = repr(fakeref)
        client.run("remove {} -c".format(repr(fakeref)), assert_error=True)
        assert client.package_exists(pref1)
        assert "Recipe revision '{}' not found".format(str_ref) in client.out

        # If I remove the ref with valid RREV, the packages are removed
        pref1 = _create(self.c_v2, self.ref)
        client.run("remove {} -c".format(repr(pref1.ref)))
        assert not client.package_exists(pref1)

        # If I remove the ref without RREV but specifying PREV it raises
        pref1 = _create(self.c_v2, self.ref)
        tmp = copy.copy(pref1.ref)
        tmp.revision = None
        command = "remove {}:{}#{} -c".format(repr(tmp), pref1.package_id, pref1.revision)
        client.run(command)
        assert not client.package_exists(pref1)

        # A wrong PREV doesn't remove the PREV
        pref1 = _create(self.c_v2, self.ref)
        command = "remove {}:{}#fakeprev -c".format(repr(pref1.ref), pref1.package_id)
        client.run(command, assert_error=True)
        assert client.package_exists(pref1)
        assert "ERROR: Package revision" in client.out

        # Everything correct, removes the unique local package revision
        pref1 = _create(self.c_v2, self.ref)
        command = "remove {}:{}#{} -c".format(repr(pref1.ref), pref1.package_id, pref1.revision)
        client.run(command)
        assert not client.package_exists(pref1)

    def test_remove_remote_recipe(self):
        """When a client removes a reference, it removes ALL revisions, no matter
        if the client is v1 or v2"""
        pref1 = _create(self.c_v2, self.ref)
        self.c_v2.run(f"upload {pref1.ref} -r=default -c")

        pref2 = _create(self.c_v2, self.ref, conanfile=GenConanfile().with_build_msg("RREV 2!"))
        self.c_v2.run(f"upload {pref2.ref} -r=default -c")

        assert pref1 != pref2

        remover_client = self.c_v2

        # Remove ref without revision in a remote
        remover_client.run("remove {} -c -r default".format(self.ref))
        assert not self.server.recipe_exists(self.ref)
        assert not self.server.recipe_exists(pref1.ref)
        assert not self.server.recipe_exists(pref2.ref)
        assert not self.server.package_exists(pref1)
        assert not self.server.package_exists(pref2)

    def test_remove_remote_recipe_revision(self):
        """If a client removes a recipe with revision:
             - If the client is v2 will remove only that revision"""
        pref1 = _create(self.c_v2, self.ref)
        self.c_v2.run(f"upload {pref1.ref} -r=default -c")

        pref2 = _create(self.c_v2, self.ref, conanfile=GenConanfile().with_build_msg("RREV 2!"))
        self.c_v2.run(f"upload {pref2.ref} -r=default -c")

        assert pref1 != pref2

        remover_client = self.c_v2

        # Remove ref without revision in a remote
        command = "remove {} -c -r default".format(repr(pref1.ref))
        remover_client.run(command)
        assert not self.server.recipe_exists(pref1.ref)
        assert self.server.recipe_exists(pref2.ref)

    def test_remove_remote_package(self):
        """When a client removes a package, without RREV, it removes the package from ALL
        RREVs"""
        pref1 = _create(self.c_v2, self.ref)
        self.c_v2.run(f"upload {pref1.ref} -r=default -c")

        pref2 = _create(self.c_v2, self.ref, conanfile=GenConanfile().with_build_msg("RREV 2!"))
        self.c_v2.run(f"upload {pref2.ref} -r=default -c")

        assert pref1.package_id == pref2.package_id
        # Both revisions exist in 2.0 cache
        assert self.c_v2.package_exists(pref1)
        assert self.c_v2.package_exists(pref2)

        remover_client = self.c_v2

        # Remove pref without RREV in a remote
        remover_client.run("remove {}#*:{} -c -r default".format(self.ref, pref2.package_id))
        assert self.server.recipe_exists(pref1.ref)
        assert self.server.recipe_exists(pref2.ref)
        assert not self.server.package_exists(pref1)
        assert not self.server.package_exists(pref2)

    def test_remove_remote_package_revision(self):
        """When a client removes a package with PREV
          (conan remove zlib/1.0@conan/stable:12312#PREV)
            - If not RREV, the client fails
            - If RREV and PREV:
                - If v1 it fails in the client (cannot transmit revisions with v1)
                - If v2 it removes only that PREV
        """
        # First RREV
        pref1 = _create(self.c_v2, self.ref)
        self.c_v2.run(f"upload {pref1.ref} -r=default -c")

        # Second RREV with two PREVS (exactly same conanfile, different package files)
        rev2_conanfile = GenConanfile().with_build_msg("RREV 2!")\
                                       .with_package_file("file", env_var="MY_VAR")
        with environment_update({"MY_VAR": "1"}):
            pref2 = _create(self.c_v2, self.ref, conanfile=rev2_conanfile)
            self.c_v2.run(f"upload {pref2.ref} -r=default -c")

        with environment_update({"MY_VAR": "2"}):
            pref2b = _create(self.c_v2, self.ref, conanfile=rev2_conanfile)
            self.c_v2.run(f"upload {pref2b.ref} -r=default -c")

        # Check created revisions
        assert pref1.package_id == pref2.package_id
        assert pref2.package_id == pref2b.package_id
        assert pref2.ref.revision == pref2b.ref.revision
        assert pref2.revision != pref2b.revision

        remover_client = self.c_v2

        # Remove PREV without RREV in a remote, the client has to fail
        command = "remove {}:{}#{} -c -r default".format(self.ref, pref2.package_id, pref2.revision)
        remover_client.run(command)

        assert self.server.recipe_exists(pref1.ref)
        assert self.server.recipe_exists(pref2.ref)
        assert self.server.recipe_exists(pref2b.ref)
        assert self.server.package_exists(pref1)
        assert self.server.package_exists(pref2b)
        assert not self.server.package_exists(pref2)

        # Try to remove a missing revision
        command = "remove {}:{}#fakerev -c -r default".format(repr(pref2.ref), pref2.package_id)
        remover_client.run(command, assert_error=True)
        fakeref = copy.copy(pref2)
        fakeref.revision = "fakerev"
        assert f"ERROR: Package revision '{fakeref.repr_notime()}' not found" in remover_client.out


class TestUploadPackagesWithRevisions:

    @pytest.fixture(autouse=True)
    def setup(self):
        self.server = TestServer()
        self.c_v2 = TestClient(servers={"default": self.server}, inputs=["admin", "password"])
        self.ref = RecipeReference.loads("lib/1.0@conan/testing")

    def test_upload_a_recipe(self):
        """If we upload a package to a server:
        Using v2 client it will upload RREV revision to the server. The rev time is NOT updated.
        """
        client = self.c_v2
        pref = _create(self.c_v2, self.ref)
        client.run(f"upload {self.ref} -r=default -c")
        revs = [r.revision for r in self.server.server_store.get_recipe_revisions_references(self.ref)]

        assert revs == [pref.ref.revision]

    def test_upload_no_overwrite_recipes(self):
        """If we upload a RREV to the server and create a new RREV in the client,
        when we upload with --no-overwrite
        Using v2 client it will warn an upload a new revision.
        """
        client = self.c_v2
        pref = _create(self.c_v2, self.ref, conanfile=GenConanfile().with_setting("os"),
                       args=" -s os=Windows")
        client.run(f"upload {self.ref} -r=default -c")

        pref2 = _create(self.c_v2, self.ref,
                        conanfile=GenConanfile().with_setting("os").with_build_msg("rev2"),
                        args=" -s os=Linux")

        assert self.server.server_store.get_last_revision(self.ref)[0] == pref.ref.revision
        client.run(f"upload {self.ref} -r=default -c")
        assert self.server.server_store.get_last_revision(self.ref)[0] == pref2.ref.revision

    def test_upload_no_overwrite_packages(self):
        """If we upload a PREV to the server and create a new PREV in the client,
        when we upload with --no-overwrite
        Using v2 client it will warn and upload a new revision.
        """
        client = self.c_v2
        conanfile = GenConanfile().with_package_file("file", env_var="MY_VAR")
        with environment_update({"MY_VAR": "1"}):
            pref = _create(self.c_v2, self.ref, conanfile=conanfile)
        client.run(f"upload {self.ref} -r=default -c")

        with environment_update({"MY_VAR": "2"}):
            pref2 = _create(self.c_v2, self.ref, conanfile=conanfile)

        assert pref.revision != pref2.revision

        assert self.server.server_store.get_last_package_revision(pref2).revision == pref.revision
        client.run(f"upload {self.ref} -r=default -c")
        assert self.server.server_store.get_last_package_revision(pref2).revision == pref2.revision


def test_server_with_only_v2_capability():
    server = TestServer(server_capabilities=[])
    c_v2 = TestClient(servers={"default": server}, inputs=["admin", "password"])
    c_v2.save({"conanfile.py": GenConanfile("pkg", "0.1")})
    c_v2.run("create .")
    c_v2.run(f"upload * -r=default -c")


class TestServerRevisionsIndexes:

    @pytest.fixture(autouse=True)
    def setup(self):
        self.server = TestServer()
        self.c_v2 = TestClient(servers={"default": self.server}, inputs=["admin", "password"])
        self.ref = RecipeReference.loads("lib/1.0@conan/testing")

    def test_rotation_deleting_recipe_revisions(self):
        """
        - If we have two RREVs in the server and we remove the first one,
        the last one is the latest
        - If we have two RREvs in the server and we remove the second one,
        the first is now the latest
        """
        ref1 = self.c_v2.export(self.ref, conanfile=GenConanfile())
        self.c_v2.run(f"upload {ref1} -r=default -c")
        assert self.server.server_store.get_last_revision(self.ref).revision == ref1.revision
        ref2 = self.c_v2.export(self.ref, conanfile=GenConanfile().with_build_msg("I'm rev2"))
        self.c_v2.run(f"upload {ref2} -r=default -c")
        assert self.server.server_store.get_last_revision(self.ref).revision == ref2.revision
        ref3 = self.c_v2.export(self.ref, conanfile=GenConanfile().with_build_msg("I'm rev3"))
        self.c_v2.run(f"upload {ref2} -r=default -c")
        assert self.server.server_store.get_last_revision(self.ref).revision == ref3.revision

        revs = [r.revision for r in self.server.server_store.get_recipe_revisions_references(self.ref)]
        assert revs == [ref3.revision, ref2.revision, ref1.revision]
        assert self.server.server_store.get_last_revision(self.ref).revision == ref3.revision

        # Delete the latest from the server
        self.c_v2.run("remove {} -r default -c".format(repr(ref3)))
        revs = [r.revision for r in self.server.server_store.get_recipe_revisions_references(self.ref)]
        assert revs == [ref2.revision, ref1.revision]
        assert self.server.server_store.get_last_revision(self.ref).revision == ref2.revision

    def test_rotation_deleting_package_revisions(self):
        """
        - If we have two PREVs in the server and we remove the first one,
        the last one is the latest
        - If we have two PREVs in the server and we remove the second one,
        the first is now the latest
        """
        conanfile = GenConanfile().with_package_file("file", env_var="MY_VAR")
        with environment_update({"MY_VAR": "1"}):
            pref1 = _create(self.c_v2, self.ref, conanfile=conanfile)
        self.c_v2.run("upload * -r=default -c")
        assert self.server.server_store.get_last_package_revision(pref1).revision == pref1.revision
        with environment_update({"MY_VAR": "2"}):
            pref2 = _create(self.c_v2, self.ref, conanfile=conanfile)
        self.c_v2.run("upload * -r=default -c")
        assert self.server.server_store.get_last_package_revision(pref1).revision == pref2.revision
        with environment_update({"MY_VAR": "3"}):
            pref3 = _create(self.c_v2, self.ref, conanfile=conanfile)
        self.c_v2.run("upload * -r=default -c")
        assert self.server.server_store.get_last_package_revision(pref1).revision == pref3.revision

        assert pref1.ref.revision == pref2.ref.revision
        assert pref2.ref.revision == pref3.ref.revision
        assert pref3.ref.revision == pref3.ref.revision

        pref = copy.copy(pref1)
        pref.revision = None
        revs = [r.revision
                for r in self.server.server_store.get_package_revisions_references(pref)]
        assert revs == [pref3.revision, pref2.revision, pref1.revision]
        assert self.server.server_store.get_last_package_revision(pref).revision == pref3.revision

        # Delete the latest from the server
        self.c_v2.run("remove {}:{}#{} -r default -c".format(repr(pref3.ref), pref3.package_id,
                                                             pref3.revision))
        revs = [r.revision
                for r in self.server.server_store.get_package_revisions_references(pref)]
        assert revs == [pref2.revision, pref1.revision]
        assert self.server.server_store.get_last_package_revision(pref).revision == pref2.revision

    def test_deleting_all_rrevs(self):
        """
        If we delete all the recipe revisions in the server. There is no latest.
        If then a client uploads a RREV it is the latest
        """
        ref1 = self.c_v2.export(self.ref, conanfile=GenConanfile())
        self.c_v2.run("upload * -r=default -c")
        ref2 = self.c_v2.export(self.ref, conanfile=GenConanfile().with_build_msg("I'm rev2"))
        self.c_v2.run("upload * -r=default -c")
        ref3 = self.c_v2.export(self.ref, conanfile=GenConanfile().with_build_msg("I'm rev3"))
        self.c_v2.run("upload * -r=default -c")

        self.c_v2.run("remove {} -r default -c".format(repr(ref1)))
        self.c_v2.run("remove {} -r default -c".format(repr(ref2)))
        self.c_v2.run("remove {} -r default -c".format(repr(ref3)))

        with pytest.raises(RecipeNotFoundException):
            self.server.server_store.get_recipe_revisions_references(self.ref)

        ref4 = self.c_v2.export(self.ref, conanfile=GenConanfile().with_build_msg("I'm rev4"))
        self.c_v2.run("upload * -r=default -c")

        revs = [r.revision for r in self.server.server_store.get_recipe_revisions_references(self.ref)]
        assert revs == [ref4.revision]

    def test_deleting_all_prevs(self):
        """
        If we delete all the package revisions in the server. There is no latest.
        If then a client uploads a RREV/PREV it is the latest
        """
        conanfile = GenConanfile().with_package_file("file", env_var="MY_VAR")
        with environment_update({"MY_VAR": "1"}):
            pref1 = _create(self.c_v2, self.ref, conanfile=conanfile)
        self.c_v2.run(f"upload {self.ref} -r=default -c")
        with environment_update({"MY_VAR": "2"}):
            pref2 = _create(self.c_v2, self.ref, conanfile=conanfile)
        self.c_v2.run(f"upload {self.ref} -r=default -c")
        with environment_update({"MY_VAR": "3"}):
            pref3 = _create(self.c_v2, self.ref, conanfile=conanfile)
        self.c_v2.run(f"upload {self.ref} -r=default -c")

        # Delete the package revisions (all of them have the same ref#rev and id)
        command = "remove {}:{}#{{}} -r default -c".format(pref3.ref.repr_notime(), pref3.package_id)
        self.c_v2.run(command.format(pref3.revision))
        self.c_v2.run(command.format(pref2.revision))
        self.c_v2.run(command.format(pref1.revision))

        with environment_update({"MY_VAR": "4"}):
            pref4 = _create(self.c_v2, self.ref, conanfile=conanfile)
        self.c_v2.run("upload {} -r default -c".format(pref4.repr_notime()))

        pref = copy.copy(pref1)
        pref.revision = None
        revs = [r.revision
                for r in self.server.server_store.get_package_revisions_references(pref)]
        assert revs == [pref4.revision]


def test_touching_other_server():
    # https://github.com/conan-io/conan/issues/9333
    servers = OrderedDict([("remote1", TestServer()),
                           ("remote2", None)])  # None server will crash if touched
    c = TestClient(servers=servers, inputs=["admin", "password"])
    c.save({"conanfile.py": GenConanfile().with_settings("os")})
    c.run("create . --name=pkg --version=0.1 --user=conan --channel=channel -s os=Windows")
    c.run("upload * -c -r=remote1")
    c.run("remove * -c")

    # This is OK, binary found
    c.run("install --requires=pkg/0.1@conan/channel -r=remote1 -s os=Windows")
    c.run("install --requires=pkg/0.1@conan/channel -r=remote1 -s os=Linux", assert_error=True)
    assert "ERROR: Missing binary: pkg/0.1@conan/channel" in c.out


@pytest.mark.artifactory_ready
def test_reupload_older_revision():
    """ upload maintains the server history
        https://github.com/conan-io/conan/issues/7331
    """
    c = TestClient(default_server_user=True)
    c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
    c.run("export .")
    rrev1 = c.exported_recipe_revision()
    c.run("upload * -r=default -c")
    c.save({"conanfile.py": GenConanfile("pkg", "0.1").with_class_attribute("potato = 42")})
    c.run("export .")
    rrev2 = c.exported_recipe_revision()
    c.run("upload * -r=default -c")

    def check_order(inverse=False):
        c.run("list pkg/0.1#* -r=default")
        out = str(c.out)
        assert rrev1 in out
        assert rrev2 in out
        if inverse:
            assert out.find(rrev1) > out.find(rrev2)
        else:
            assert out.find(rrev1) < out.find(rrev2)

    check_order()

    # If we create the same older revision, and upload, still the same order
    c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
    c.run("export .")
    c.run("upload * -r=default -c")
    check_order()

    # Force doesn't change it, same order
    c.run("upload * -r=default -c --force")
    check_order()

    # the only way is to remove it, then upload
    c.run(f"remove pkg/0.1#{rrev1} -r=default -c")
    c.run("upload * -r=default -c --force")
    check_order(inverse=True)


@pytest.mark.artifactory_ready
def test_reupload_older_revision_new_binaries():
    """ upload maintains the server history
        https://github.com/conan-io/conan/pull/16621
    """
    c = TestClient(default_server_user=True)
    c.save({"conanfile.py": GenConanfile("pkg", "0.1").with_settings("os")})
    c.run("create . -s os=Linux")
    rrev1 = c.exported_recipe_revision()
    c.run("upload * -r=default -c")
    c.save({"conanfile.py": GenConanfile("pkg", "0.1").with_settings("os")
                                                      .with_class_attribute("potato = 42")})
    c.run("create . -s os=Linux")
    rrev2 = c.exported_recipe_revision()
    c.run("upload * -r=default -c")

    def check_order():
        c.run("list pkg/0.1#* -r=default")
        out = str(c.out)
        assert rrev1 in out
        assert rrev2 in out
        assert out.find(rrev1) < out.find(rrev2)

    check_order()

    # If we create the same older revision, and upload, still the same order
    # c.run("remove * -c")  # Make sure no other revision
    c.save({"conanfile.py": GenConanfile("pkg", "0.1").with_settings("os")})
    c.run("create . -s os=Windows")
    rrev3 = c.exported_recipe_revision()
    assert rrev3 == rrev1
    c.run(f"upload pkg*#{rrev3} -r=default -c --force")
    check_order()
