test_remote_url() {
    # shellcheck disable=2153
    for url in "${INCUS_ADDR}" "https://${INCUS_ADDR}"; do
        token="$(incus config trust add foo -q)"
        incus_remote remote add test "${url}" --accept-certificate --token "${token}"
        incus_remote info test:
        incus_remote config trust list -cf -fcsv | while read -r line; do
            incus_remote config trust remove "\"${line}\""
        done
        incus_remote remote remove test
    done

    # shellcheck disable=2153
    urls="${INCUS_DIR}/unix.socket unix:${INCUS_DIR}/unix.socket unix://${INCUS_DIR}/unix.socket"
    if [ -z "${INCUS_OFFLINE:-}" ]; then
        urls="https://images.linuxcontainers.org ${urls}"
    fi

    for url in ${urls}; do
        # an invalid protocol returns an error
        ! incus_remote remote add test "${url}" --accept-certificate --token foo --protocol foo || false

        if echo "${url}" | grep -q linuxcontainers.org; then
            incus_remote remote add test "${url}" --protocol=simplestreams
        else
            incus_remote remote add test "${url}"
        fi

        incus_remote remote remove test
    done
}

test_remote_url_with_token() {
    # Try adding remote using a correctly constructed but invalid token
    invalid_token="eyJjbGllbnRfbmFtZSI6IiIsImZpbmdlcnByaW50IjoiMWM0MmMzOTgxOWIyNGJiYjQxNGFhYTY2NDUwNzlmZGY2NDQ4MTUzMDcxNjA0YTFjODJjMjVhN2JhNjBkZmViMCIsImFkZHJlc3NlcyI6WyIxOTIuMTY4LjE3OC4yNDo4NDQzIiwiWzIwMDM6Zjc6MzcxMToyMzAwOmQ5ZmY6NWRiMDo3ZTA2OmQ1ODldOjg0NDMiLCIxMC41OC4yLjE6ODQ0MyIsIjEwLjAuMy4xOjg0NDMiLCIxOTIuMTY4LjE3OC43MDo4NDQzIiwiWzIwMDM6Zjc6MzcxMToyMzAwOjQwMTY6ODVkNDo2M2FlOjNhYWVdOjg0NDMiLCIxMC4xMjQuODYuMTo4NDQzIiwiW2ZkNDI6ZTY5Zjo3OTczOjIyMjU6OjFdOjg0NDMiXSwic2VjcmV0IjoiODVlMGU5YmViODk0ZTFhMTU3YmYxODI4YTk0Y2IwYTdjY2YxMzQ4NzMyN2ZjMTY3MDcyY2JlNjQ3NmVmOGJkMiJ9"
    ! incus_remote remote add test "${invalid_token}" || false

    # Generate token for client foo
    incus config trust add foo -q

    # Listing all tokens should show only a single one
    [ "$(incus config trust list-tokens -f json | jq '[.[] | select(.ClientName == "foo")] |  length')" -eq 1 ]

    # Extract token
    token="$(incus config trust list-tokens -f json | jq '.[].Token')"

    # Invalidate token so that it cannot be used again
    incus config trust revoke-token foo

    # Ensure the token is invalidated
    [ "$(incus config trust list-tokens -f json | jq 'length')" -eq 0 ]

    # Try adding the remote using the invalidated token
    ! incus_remote remote add test "${token}" || false

    # Generate token for client foo
    incus project create foo
    incus config trust add -q --projects foo --restricted foo

    # Extract the token
    token="$(incus config trust list-tokens -f json | jq -r '.[].Token')"

    # Add the valid token
    incus_remote remote add test "${token}"

    # Ensure the token is invalidated
    [ "$(incus config trust list-tokens -f json | jq 'length')" -eq 0 ]

    # List instances as the remote has been added
    incus_remote ls test:

    # Clean up
    incus_remote remote remove test
    incus incus config trust list -cf -fcsv | while read -r line; do
        incus config trust remove "\"${line}\""
    done

    # Generate new token
    incus config trust add -q foo

    # Extract token
    token="$(incus config trust list-tokens -f json | jq '.[].Token')"

    # create new certificate
    gen_cert_and_key "${TEST_DIR}/token-client.key" "${TEST_DIR}/token-client.crt" "incus.local"

    # Try accessing instances (this should fail)
    [ "$(curl -k -s --key "${TEST_DIR}/token-client.key" --cert "${TEST_DIR}/token-client.crt" "https://${INCUS_ADDR}/1.0/instances" | jq '.error_code')" -eq 403 ]

    # Add valid token
    curl -k -s --key "${TEST_DIR}/token-client.key" --cert "${TEST_DIR}/token-client.crt" -X POST -d "{\"token\": ${token}}" "https://${INCUS_ADDR}/1.0/certificates"

    # Check if we can see instances
    [ "$(curl -k -s --key "${TEST_DIR}/token-client.key" --cert "${TEST_DIR}/token-client.crt" "https://${INCUS_ADDR}/1.0/instances" | jq '.status_code')" -eq 200 ]

    incus incus config trust list -cf -fcsv | while read -r line; do
        incus config trust remove "\"${line}\""
    done

    # Generate new token
    incus config trust add -q --projects foo --restricted foo

    # Extract token
    token="$(incus config trust list-tokens -f json | jq '.[].Token')"

    # Add valid token but override projects
    curl -k -s --key "${TEST_DIR}/token-client.key" --cert "${TEST_DIR}/token-client.crt" -X POST -d "{\"token\":${token},\"projects\":[\"default\",\"foo\"],\"restricted\":false}" "https://${INCUS_ADDR}/1.0/certificates"

    # Check if we can see instances in the foo project
    [ "$(curl -k -s --key "${TEST_DIR}/token-client.key" --cert "${TEST_DIR}/token-client.crt" "https://${INCUS_ADDR}/1.0/instances?project=foo" | jq '.status_code')" -eq 200 ]

    # Check if we can see instances in the default project (this should fail)
    [ "$(curl -k -s --key "${TEST_DIR}/token-client.key" --cert "${TEST_DIR}/token-client.crt" "https://${INCUS_ADDR}/1.0/instances" | jq '.error_code')" -eq 403 ]

    incus incus config trust list -cf -fcsv | while read -r line; do
        incus config trust remove "\"${line}\""
    done

    # Set token expiry to 5 seconds
    incus config set core.remote_token_expiry 5S

    # Generate new token
    token="$(incus config trust add foo | tail -n1)"

    # Try adding remote. This should succeed.
    incus_remote remote add test "${token}"

    # Remove all trusted clients
    incus incus config trust list -cf -fcsv | while read -r line; do
        incus config trust remove "\"${line}\""
    done

    # Remove remote
    incus_remote remote rm test

    # Generate new token
    token="$(incus config trust add foo | tail -n1)"

    # This will cause the token to expire
    sleep 5

    # Try adding remote. This should fail.
    ! incus_remote remote add test "${token}" || false

    # Unset token expiry
    incus config unset core.remote_token_expiry
}

test_remote_admin() {
    # Clear all certificates
    for fp in $(incus config trust list -fcsv -cf); do incus config trust remove "${fp}"; done

    ! incus_remote remote add badpass "${INCUS_ADDR}" --accept-certificate --token badtoken || false
    ! incus_remote list badpass: || false

    token="$(incus config trust add foo -q)"
    incus_remote remote add foo "${INCUS_ADDR}" --accept-certificate --token "${token}"
    incus_remote remote list | grep 'foo'

    incus_remote remote set-default foo
    [ "$(incus_remote remote get-default)" = "foo" ]

    incus_remote remote rename foo bar
    incus_remote remote list | grep 'bar'
    incus_remote remote list | grep -v 'foo'
    [ "$(incus_remote remote get-default)" = "bar" ]

    ! incus_remote remote remove bar || false
    incus_remote remote set-default local
    incus_remote remote remove bar

    # This is a test for #91, we expect this to block asking for a token if we
    # tried to re-add our cert.
    echo y | incus_remote remote add foo "${INCUS_ADDR}"
    incus_remote remote remove foo

    # we just re-add our cert under a different name to test the cert
    # manipulation mechanism.
    gen_cert client2

    # Test for #623
    token="$(incus config trust add foo -q)"
    incus_remote remote add test-623 "${INCUS_ADDR}" --accept-certificate --token "${token}"
    incus_remote remote remove test-623

    # now re-add under a different alias
    incus_remote config trust add-certificate "${INCUS_CONF}/client2.crt"
    if [ "$(incus_remote config trust list -fcsv -cn | wc -l)" -ne 2 ]; then
        echo "wrong number of certs"
        false
    fi

    # Check that we can add domains with valid certs without confirmation:
    if [ -z "${INCUS_OFFLINE:-}" ]; then
        incus_remote remote add images1 https://images.linuxcontainers.org --protocol=simplestreams
        incus_remote remote remove images1
    fi
}

test_remote_usage() {
    # Clear all certificates
    for fp in $(incus config trust list -fcsv -cf); do incus config trust remove "${fp}"; done

    # shellcheck disable=2039,3043
    local INCUS2_DIR INCUS2_ADDR
    INCUS2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
    chmod +x "${INCUS2_DIR}"
    spawn_incus "${INCUS2_DIR}" true
    INCUS2_ADDR=$(cat "${INCUS2_DIR}/incus.addr")

    ensure_import_testimage
    ensure_has_localhost_remote "${INCUS_ADDR}"

    token="$(INCUS_DIR=${INCUS2_DIR} incus config trust add foo -q)"
    incus_remote remote add incus2 "${INCUS2_ADDR}" --accept-certificate --token "${token}"

    # we need a public image on localhost

    incus_remote image export localhost:testimage "${INCUS_DIR}/foo"
    incus_remote image delete localhost:testimage
    sum=$(sha256sum "${INCUS_DIR}/foo.tar.xz" | cut -d' ' -f1)
    incus_remote image import "${INCUS_DIR}/foo.tar.xz" localhost: --public
    incus_remote image alias create localhost:testimage "${sum}"

    incus_remote image delete "incus2:${sum}" || true

    incus_remote image copy localhost:testimage incus2: --copy-aliases --public
    incus_remote image delete "localhost:${sum}"
    incus_remote image copy "incus2:${sum}" local: --copy-aliases --public
    incus_remote image info localhost:testimage
    incus_remote image delete "incus2:${sum}"

    incus_remote image copy "localhost:${sum}" incus2:
    incus_remote image delete "incus2:${sum}"

    incus_remote image copy "localhost:$(echo "${sum}" | colrm 3)" incus2:
    incus_remote image delete "incus2:${sum}"

    # test a private image
    incus_remote image copy "localhost:${sum}" incus2:
    incus_remote image delete "localhost:${sum}"
    incus_remote init "incus2:${sum}" localhost:c1
    incus_remote delete localhost:c1

    incus_remote image alias create localhost:testimage "${sum}"

    # test remote publish
    incus_remote init testimage pub
    incus_remote publish pub incus2: --alias bar --public a=b
    incus_remote image show incus2:bar | grep -q "a: b"
    incus_remote image show incus2:bar | grep -q "public: true"
    ! incus_remote image show bar || false
    incus_remote delete pub

    # test spawn from public server
    incus_remote remote add incus2-public "${INCUS2_ADDR}" --public --accept-certificate
    incus_remote init incus2-public:bar pub
    incus_remote image delete incus2:bar
    incus_remote delete pub

    # Double launch to test if the image downloads only once.
    incus_remote init localhost:testimage incus2:c1 &
    C1PID=$!

    incus_remote init localhost:testimage incus2:c2
    incus_remote delete incus2:c2

    wait "${C1PID}"
    incus_remote delete incus2:c1

    # launch testimage stored on localhost as container c1 on incus2
    incus_remote launch localhost:testimage incus2:c1

    # make sure it is running
    incus_remote list incus2: | grep c1 | grep RUNNING
    incus_remote info incus2:c1
    incus_remote stop incus2:c1 --force
    incus_remote delete incus2:c1

    # Test that local and public servers can be accessed without a client cert
    mv "${INCUS_CONF}/client.crt" "${INCUS_CONF}/client.crt.bak"
    mv "${INCUS_CONF}/client.key" "${INCUS_CONF}/client.key.bak"

    # testimage should still exist on the local server.
    incus_remote image list local: | grep -q testimage

    # Skip the truly remote servers in offline mode.  There should always be
    # Ubuntu images in the results for the remote servers.
    if [ -z "${INCUS_OFFLINE:-}" ]; then
        incus_remote image list images: | grep -i -c ubuntu
    fi

    mv "${INCUS_CONF}/client.crt.bak" "${INCUS_CONF}/client.crt"
    mv "${INCUS_CONF}/client.key.bak" "${INCUS_CONF}/client.key"

    incus_remote image delete "incus2:${sum}"

    incus_remote image alias create localhost:foo "${sum}"

    incus_remote image copy "localhost:${sum}" incus2: --mode=push
    incus_remote image show incus2:"${sum}"
    incus_remote image show incus2:"${sum}" | grep -q 'public: false'
    ! incus_remote image show incus2:foo || false
    incus_remote image delete "incus2:${sum}"

    incus_remote image copy "localhost:${sum}" incus2: --mode=push --copy-aliases --public
    incus_remote image show incus2:"${sum}"
    incus_remote image show incus2:"${sum}" | grep -q 'public: true'
    incus_remote image show incus2:foo
    incus_remote image delete "incus2:${sum}"

    incus_remote image copy "localhost:${sum}" incus2: --mode=push --copy-aliases --alias=bar
    incus_remote image show incus2:"${sum}"
    incus_remote image show incus2:foo
    incus_remote image show incus2:bar
    incus_remote image delete "incus2:${sum}"

    incus_remote image copy "localhost:${sum}" incus2: --mode=relay
    incus_remote image show incus2:"${sum}"
    incus_remote image show incus2:"${sum}" | grep -q 'public: false'
    ! incus_remote image show incus2:foo || false
    incus_remote image delete "incus2:${sum}"

    incus_remote image copy "localhost:${sum}" incus2: --mode=relay --copy-aliases --public
    incus_remote image show incus2:"${sum}"
    incus_remote image show incus2:"${sum}" | grep -q 'public: true'
    incus_remote image show incus2:foo
    incus_remote image delete "incus2:${sum}"

    incus_remote image copy "localhost:${sum}" incus2: --mode=relay --copy-aliases --alias=bar
    incus_remote image show incus2:"${sum}"
    incus_remote image show incus2:foo
    incus_remote image show incus2:bar
    incus_remote image delete "incus2:${sum}"

    # Test image copy between projects
    incus_remote project create incus2:foo
    incus_remote image copy "localhost:${sum}" incus2: --target-project foo
    incus_remote image show incus2:"${sum}" --project foo
    incus_remote image delete "incus2:${sum}" --project foo
    incus_remote image copy "localhost:${sum}" incus2: --target-project foo --mode=push
    incus_remote image show incus2:"${sum}" --project foo
    incus_remote image delete "incus2:${sum}" --project foo
    incus_remote image copy "localhost:${sum}" incus2: --target-project foo --mode=relay
    incus_remote image show incus2:"${sum}" --project foo
    incus_remote image delete "incus2:${sum}" --project foo
    incus_remote project delete incus2:foo

    # Test image copy with --profile option
    incus_remote profile create incus2:foo
    incus_remote image copy "localhost:${sum}" incus2: --profile foo
    incus_remote image show incus2:"${sum}" | grep -q '\- foo'
    incus_remote image delete "incus2:${sum}"

    incus_remote image copy "localhost:${sum}" incus2: --profile foo --mode=push
    incus_remote image show incus2:"${sum}" | grep -q '\- foo'
    incus_remote image delete "incus2:${sum}"

    incus_remote image copy "localhost:${sum}" incus2: --profile foo --mode=relay
    incus_remote image show incus2:"${sum}" | grep -q '\- foo'
    incus_remote image delete "incus2:${sum}"
    incus_remote profile delete incus2:foo

    incus_remote image alias delete localhost:foo

    incus_remote remote remove incus2
    incus_remote remote remove incus2-public

    kill_incus "$INCUS2_DIR"

    # Cleanup the image from the original remote
    incus image delete testimage
}

test_list_all_remotes() {
    ensure_import_testimage
    # shellcheck disable=2153
    ensure_has_localhost_remote "${INCUS_ADDR}"

    # Spawn a second daemon and add it as a remote.
    # shellcheck disable=2039,3043
    local INCUS2_DIR INCUS2_ADDR
    INCUS2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
    chmod +x "${INCUS2_DIR}"
    spawn_incus "${INCUS2_DIR}" true
    INCUS2_ADDR=$(cat "${INCUS2_DIR}/incus.addr")

    token="$(INCUS_DIR=${INCUS2_DIR} incus config trust add foo -q)"
    incus_remote remote add incus2 "${INCUS2_ADDR}" --accept-certificate --token "${token}"

    # Create an instance on each remote.
    incus_remote init localhost:testimage localhost:c1
    incus_remote init localhost:testimage incus2:c2

    # Both instances show up with their remote.
    incus_remote list --all-remotes | grep -q "REMOTE"
    incus_remote list --all-remotes -f csv -c Rn | grep -q "^localhost,c1$"
    incus_remote list --all-remotes -f csv -c Rn | grep -q "^incus2,c2$"

    # Custom columns don't get the remote column added.
    ! incus_remote list --all-remotes -c ns | grep -q "REMOTE" || false

    # JSON output includes the remote name.
    [ "$(incus_remote list --all-remotes -f json | jq -r '.[] | select(.name == "c2") | .remote')" = "incus2" ]

    # Combining with --all-projects.
    incus_remote list --all-remotes --all-projects | grep -q "PROJECT"

    # A remote can't be specified together with --all-remotes.
    ! incus_remote list localhost: --all-remotes || false

    # Cleanup.
    incus_remote delete localhost:c1
    incus_remote delete incus2:c2
    incus_remote remote remove incus2
    kill_incus "${INCUS2_DIR}"
}
