module RegistryTests
import ..Pkg # ensure we are using the correct Pkg

using Pkg, UUIDs, LibGit2, Test
using Pkg: depots1
using Pkg.REPLMode: pkgstr
using Pkg.Types: PkgError, manifest_info, PackageSpec, EnvCache

using ..Utils


function setup_test_registries(dir = pwd())
    # Set up two registries with the same name, with different uuid
    pkg_uuids = ["c5f1542f-b8aa-45da-ab42-05303d706c66", "d7897d3a-8e65-4b65-bdc8-28ce4e859565"]
    reg_uuids = ["e9fceed0-5623-4384-aff0-6db4c442647a", "a8e078ad-b4bd-4e09-a52f-c464826eef9d"]
    for i in 1:2
        regpath = joinpath(dir, "RegistryFoo$(i)")
        mkpath(joinpath(regpath, "Example"))
        write(
            joinpath(regpath, "Registry.toml"), """
            name = "RegistryFoo"
            uuid = "$(reg_uuids[i])"
            repo = "https://github.com"
            [packages]
            $(pkg_uuids[i]) = { name = "Example$(i)", path = "Example" }
            """
        )
        write(
            joinpath(regpath, "Example", "Package.toml"), """
            name = "Example$(i)"
            uuid = "$(pkg_uuids[i])"
            repo = "https://github.com/JuliaLang/Example.jl.git"
            """
        )
        write(
            joinpath(regpath, "Example", "Versions.toml"), """
            ["0.5.1"]
            git-tree-sha1 = "8eb7b4d4ca487caade9ba3e85932e28ce6d6e1f8"
            """
        )
        write(
            joinpath(regpath, "Example", "Deps.toml"), """
            ["0.5"]
            julia = "0.6-1.0"
            """
        )
        write(
            joinpath(regpath, "Example", "Compat.toml"), """
            ["0.5"]
            julia = "0.6-1.0"
            """
        )
        git_init_and_commit(regpath)
    end
    return
end

function test_installed(registries)
    return @test setdiff(
        UUID[r.uuid for r in registries],
        UUID[r.uuid for r in Pkg.Registry.reachable_registries()]
    ) == UUID[]
end

function is_pkg_available(pkg::PackageSpec)
    uuids = Set{UUID}()
    for registry in Pkg.Registry.reachable_registries()
        union!(uuids, keys(registry))
    end
    return in(pkg.uuid, uuids)
end

function with_depot2(f)
    Base.DEPOT_PATH[1:2] .= Base.DEPOT_PATH[2:-1:1]
    f()
    return Base.DEPOT_PATH[1:2] .= Base.DEPOT_PATH[2:-1:1]
end

@testset "registries" begin
    temp_pkg_dir() do depot
        mktempdir() do depot2
            insert!(Base.DEPOT_PATH, 2, depot2)
            # set up registries
            regdir = mktempdir()
            setup_test_registries(regdir)
            general_url = Pkg.Registry.DEFAULT_REGISTRIES[1].url
            general_path = Pkg.Registry.DEFAULT_REGISTRIES[1].path
            general_linked = Pkg.Registry.DEFAULT_REGISTRIES[1].linked
            General = RegistrySpec(
                name = "General", uuid = "23338594-aafe-5451-b93e-139f81909106",
                url = general_url, path = general_path, linked = general_linked
            )
            Foo1 = RegistrySpec(
                name = "RegistryFoo", uuid = "e9fceed0-5623-4384-aff0-6db4c442647a",
                url = joinpath(regdir, "RegistryFoo1")
            )
            Foo2 = RegistrySpec(
                name = "RegistryFoo", uuid = "a8e078ad-b4bd-4e09-a52f-c464826eef9d",
                url = joinpath(regdir, "RegistryFoo2")
            )

            # Packages in registries
            Example = PackageSpec(name = "Example", uuid = UUID("7876af07-990d-54b4-ab0e-23690620f79a"))
            Example1 = PackageSpec(name = "Example1", uuid = UUID("c5f1542f-b8aa-45da-ab42-05303d706c66"))
            Example2 = PackageSpec(name = "Example2", uuid = UUID("d7897d3a-8e65-4b65-bdc8-28ce4e859565"))

            # Add General registry
            ## Pkg REPL
            for reg in (
                    "General",
                    "23338594-aafe-5451-b93e-139f81909106",
                    "General=23338594-aafe-5451-b93e-139f81909106",
                )
                pkgstr("registry add $(reg)")
                test_installed([General])

                pkgstr("registry up $(reg)")
                test_installed([General])
                pkgstr("registry rm $(reg)")
                test_installed([])
            end

            ## Pkg REPL without argument
            pkgstr("registry add")
            test_installed([General])
            pkgstr("registry rm General")
            test_installed([])

            ## Registry API
            for reg in (
                    "General",
                    RegistrySpec("General"),
                    RegistrySpec(name = "General"),
                    RegistrySpec(name = "General", path = general_path),
                    RegistrySpec(uuid = "23338594-aafe-5451-b93e-139f81909106"),
                    RegistrySpec(name = "General", uuid = "23338594-aafe-5451-b93e-139f81909106"),
                )
                Pkg.Registry.add(reg)
                test_installed([General])
                @test is_pkg_available(Example)
                Pkg.Registry.update(reg)
                test_installed([General])
                Pkg.Registry.rm(reg)
                test_installed([])
                @test !is_pkg_available(Example)
            end

            # Add registry from URL/local path.
            pkgstr("registry add $(Foo1.url)")
            test_installed([Foo1])
            @test is_pkg_available(Example1)
            @test !is_pkg_available(Example2)
            with_depot2(() -> pkgstr("registry add $(Foo2.url)"))
            test_installed([Foo1, Foo2])
            @test is_pkg_available(Example1)
            @test is_pkg_available(Example2)

            # reset installed registries
            rm.(joinpath.(Base.DEPOT_PATH[1:2], "registries"); force = true, recursive = true)

            Registry.add(url = Foo1.url)
            test_installed([Foo1])
            @test is_pkg_available(Example1)
            @test !is_pkg_available(Example2)
            with_depot2(() -> Registry.add(url = Foo2.url))
            test_installed([Foo1, Foo2])
            @test is_pkg_available(Example1)
            @test is_pkg_available(Example2)


            pkgstr("registry up $(Foo1.uuid)")
            pkgstr("registry update $(Foo1.name)=$(Foo1.uuid)")
            Registry.update(uuid = Foo1.uuid)
            Registry.update(name = Foo1.name, uuid = Foo1.uuid)

            test_installed([Foo1, Foo2])
            pkgstr("registry rm $(Foo1.uuid)")
            test_installed([Foo2])
            @test !is_pkg_available(Example1)
            @test is_pkg_available(Example2)
            Registry.add(url = Foo1.url)
            test_installed([Foo1, Foo2])
            @test is_pkg_available(Example1)
            @test is_pkg_available(Example2)
            pkgstr("registry rm $(Foo1.name)=$(Foo1.uuid)")
            test_installed([Foo2])
            @test !is_pkg_available(Example1)
            @test is_pkg_available(Example2)
            with_depot2() do
                pkgstr("registry rm $(Foo2.name)")
            end
            test_installed([])
            @test !is_pkg_available(Example1)
            @test !is_pkg_available(Example2)

            Registry.add(url = Foo1.url)
            with_depot2(() -> Registry.add(url = Foo2.url))
            test_installed([Foo1, Foo2])
            @test is_pkg_available(Example1)
            @test is_pkg_available(Example2)
            Registry.rm(uuid = Foo1.uuid)
            test_installed([Foo2])
            @test !is_pkg_available(Example1)
            @test is_pkg_available(Example2)
            Registry.add(url = Foo1.url)
            test_installed([Foo1, Foo2])
            @test is_pkg_available(Example1)
            @test is_pkg_available(Example2)
            Registry.rm(name = Foo1.name, uuid = Foo1.uuid)
            test_installed([Foo2])
            @test !is_pkg_available(Example1)
            @test is_pkg_available(Example2)
            with_depot2() do
                Registry.rm(Foo2.name)
            end
            test_installed([])
            @test !is_pkg_available(Example1)
            @test !is_pkg_available(Example2)

            # multiple registries on the same time
            pkgstr("registry add General $(Foo1.url)")
            with_depot2(() -> pkgstr("registry add $(Foo2.url)"))
            test_installed([General, Foo1, Foo2])
            @test is_pkg_available(Example)
            @test is_pkg_available(Example1)
            @test is_pkg_available(Example2)
            pkgstr("registry up General $(Foo1.uuid) $(Foo2.name)=$(Foo2.uuid)")
            pkgstr("registry rm General $(Foo1.uuid)")
            with_depot2() do
                pkgstr("registry rm General $(Foo2.name)=$(Foo2.uuid)")
            end
            test_installed([])
            @test !is_pkg_available(Example)
            @test !is_pkg_available(Example1)
            @test !is_pkg_available(Example2)

            Registry.add(
                [
                    RegistrySpec("General"),
                    RegistrySpec(url = Foo1.url),
                ]
            )
            with_depot2(() -> Registry.add([RegistrySpec(url = Foo2.url)]))
            test_installed([General, Foo1, Foo2])
            @test is_pkg_available(Example)
            @test is_pkg_available(Example1)
            @test is_pkg_available(Example2)
            Registry.update(
                [
                    RegistrySpec("General"),
                    RegistrySpec(uuid = Foo1.uuid),
                    RegistrySpec(name = Foo2.name, uuid = Foo2.uuid),
                ]
            )
            Registry.rm(
                [
                    RegistrySpec("General"),
                    RegistrySpec(uuid = Foo1.uuid),
                ]
            )
            with_depot2() do
                Registry.rm(name = Foo2.name, uuid = Foo2.uuid)
            end
            test_installed([])
            @test !is_pkg_available(Example)
            @test !is_pkg_available(Example1)
            @test !is_pkg_available(Example2)

            # Trying to add a registry with the same name as existing one
            pkgstr("registry add $(Foo1.url)")
            @test_throws PkgError pkgstr("registry add $(Foo2.url)")
            @test_throws PkgError Registry.add([RegistrySpec(url = Foo2.url)])

        end
    end

    # issue #711
    temp_pkg_dir() do depot
        mktempdir() do depot2
            insert!(Base.DEPOT_PATH, 2, depot2)
            Registry.add("General")
            with_depot2(() -> Registry.add("General"))
            # This add should not error because depot/Example and depot2/Example have the same uuid
            Pkg.add("Example")
            @test isinstalled((name = "Example", uuid = UUID("7876af07-990d-54b4-ab0e-23690620f79a")))
        end
    end

    # Test Registry.add and Registry.update with explicit depot values
    temp_pkg_dir() do depot_on_path
        mktempdir() do depot_off_path
            # No registries anywhere
            @test isempty(Registry.reachable_registries())
            @test isempty(Registry.reachable_registries(; depots = [depot_off_path]))

            # After this, we have depots only in the depot that's off the path
            Registry.add("General"; depots = depot_off_path)
            @test isempty(Registry.reachable_registries())
            @test length(Registry.reachable_registries(; depots = [depot_off_path])) == 1

            # Test that `update()` with `depots` runs
            io = Base.BufferStream()
            Registry.update(; depots = [depot_off_path], io)
            closewrite(io)
            output = read(io, String)
            @test occursin("registry at `$(depot_off_path)", output)

            # Show that we can install `Example` off of that depot
            empty!(Base.DEPOT_PATH)
            push!(Base.DEPOT_PATH, depot_off_path)
            Pkg.add("Example")
            @test isinstalled((name = "Example", uuid = UUID("7876af07-990d-54b4-ab0e-23690620f79a")))
        end
    end

    # only clone default registry if there are no registries installed at all
    temp_pkg_dir() do depot1
        mktempdir() do depot2
            append!(empty!(DEPOT_PATH), [depot1, depot2])
            Base.append_bundled_depot_path!(DEPOT_PATH)
            @test length(Pkg.Registry.reachable_registries()) == 0
            Pkg.add("Example")
            @test length(Pkg.Registry.reachable_registries()) == 1
            Pkg.rm("Example")
            DEPOT_PATH[1:2] .= DEPOT_PATH[2:-1:1]
            Pkg.add("Example") # should not trigger a clone of default registries
            @test length(Pkg.Registry.reachable_registries()) == 1
        end
    end

    @testset "yanking" begin
        uuid = Base.UUID("7876af07-990d-54b4-ab0e-23690620f79a") # Example
        # Tests that Example@0.5.1 does not get installed
        temp_pkg_dir() do env
            Pkg.Registry.add(url = "https://github.com/JuliaRegistries/Test")
            Pkg.add("Example")
            @test manifest_info(EnvCache().manifest, uuid).version == v"0.5.0"
            Pkg.update() # should not update Example
            @test manifest_info(EnvCache().manifest, uuid).version == v"0.5.0"
            @test_throws Pkg.Resolve.ResolverError Pkg.add(PackageSpec(name = "Example", version = v"0.5.1"))
            Pkg.rm("Example")
            Pkg.add("JSON") # depends on Example
            @test manifest_info(EnvCache().manifest, uuid).version == v"0.5.0"
            Pkg.update()
            @test manifest_info(EnvCache().manifest, uuid).version == v"0.5.0"
        end
        # Test that Example@0.5.1 can be obtained from an existing manifest
        temp_pkg_dir() do env
            Pkg.Registry.add(url = "https://github.com/JuliaRegistries/Test")
            write(
                joinpath(env, "Project.toml"), """
                [deps]
                Example = "7876af07-990d-54b4-ab0e-23690620f79a"
                """
            )
            write(
                joinpath(env, "Manifest.toml"), """
                [[Example]]
                git-tree-sha1 = "8eb7b4d4ca487caade9ba3e85932e28ce6d6e1f8"
                uuid = "7876af07-990d-54b4-ab0e-23690620f79a"
                version = "0.5.1"
                """
            )
            Pkg.activate(env)
            Pkg.instantiate()
            @test manifest_info(EnvCache().manifest, uuid).version == v"0.5.1"
        end
        temp_pkg_dir() do env
            Pkg.Registry.add(url = "https://github.com/JuliaRegistries/Test")
            write(
                joinpath(env, "Project.toml"), """
                [deps]
                JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
                """
            )
            write(
                joinpath(env, "Manifest.toml"), """
                [[Example]]
                git-tree-sha1 = "8eb7b4d4ca487caade9ba3e85932e28ce6d6e1f8"
                uuid = "7876af07-990d-54b4-ab0e-23690620f79a"
                version = "0.5.1"

                [[JSON]]
                deps = ["Example"]
                git-tree-sha1 = "1f7a25b53ec67f5e9422f1f551ee216503f4a0fa"
                uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
                version = "0.20.0"
                """
            )
            Pkg.activate(env)
            Pkg.instantiate()
            @test manifest_info(EnvCache().manifest, uuid).version == v"0.5.1"
        end
    end
end

if Pkg.Registry.registry_use_pkg_server()
    @testset "compressed registry" begin
        for unpack in (true, nothing)
            withenv("JULIA_PKG_UNPACK_REGISTRY" => unpack) do
                temp_pkg_dir(; linked_reg = false) do depot
                    # These get restored by temp_pkg_dir
                    Pkg.Registry.DEFAULT_REGISTRIES[1].path = nothing
                    Pkg.Registry.DEFAULT_REGISTRIES[1].url = "https://github.com/JuliaRegistries/General.git"

                    # This should not uncompress the registry
                    Registry.add(uuid = UUID("23338594-aafe-5451-b93e-139f81909106"))
                    @test isfile(joinpath(DEPOT_PATH[1], "registries", "General.tar.gz")) != something(unpack, false)
                    Pkg.add("Example")

                    # Write some bad git-tree-sha1 here so that Pkg.update will have to update the registry
                    if unpack == true
                        write(
                            joinpath(DEPOT_PATH[1], "registries", "General", ".tree_info.toml"),
                            """
                            git-tree-sha1 = "179182faa6a80b3cf24445e6f55c954938d57941"
                            """
                        )
                    else
                        write(
                            joinpath(DEPOT_PATH[1], "registries", "General.toml"),
                            """
                            git-tree-sha1 = "179182faa6a80b3cf24445e6f55c954938d57941"
                            uuid = "23338594-aafe-5451-b93e-139f81909106"
                            path = "General.tar.gz"
                            """
                        )
                    end
                    Pkg.update()
                    Pkg.Registry.rm(name = "General")
                    @test isempty(readdir(joinpath(DEPOT_PATH[1], "registries")))
                end
            end
        end
    end
end

end # module
