#!/bin/bash

set -e

STORE_CONFIG=/etc/systemd/system/snapd.service.d/store.conf

show_help() {
    echo "usage: store-state setup-fake-store <DIR>"
    echo "       store-state teardown-fake-store <DIR>"
    echo "       store-state setup-staging-store"
    echo "       store-state teardown-staging-store"
    echo "       store-state make-snap-installable [--noack ] [--extra-decl-json FILE] <DIR> <SNAP_PATH> [SNAP_ID]"
    echo "       store-state make-component-installable --snap-id <ID> --component-revision <REV> --snap-revision <REV> [--noack ] <DIR> <SNAP_PATH>"
    echo "       store-state init-fake-refreshes <DIR>"
    echo "       store-state add-to-channel <DIR> <FILENAME> <CHANNEL>"
}

_configure_store_backends(){
    systemctl stop snapd.service snapd.socket
    mkdir -p "$(dirname $STORE_CONFIG)"
    rm -f "$STORE_CONFIG"
    cat > "$STORE_CONFIG" <<EOF
[Service]
Environment=SNAPD_DEBUG=1 SNAPD_DEBUG_HTTP=7 SNAPPY_TESTING=1
Environment=$*
EOF
    systemctl daemon-reload
    systemctl start snapd.socket
}

setup_staging_store(){
    _configure_store_backends "SNAPPY_USE_STAGING_STORE=1"
}

teardown_staging_store(){
    systemctl stop snapd.service snapd.socket
    rm -f "$STORE_CONFIG"
    systemctl daemon-reload
    systemctl start snapd.socket
}

init_fake_refreshes(){
    local dir="$1"
    shift

    fakestore make-refreshable --dir "$dir" "$@"
}

make_snap_installable(){
    local ack=true
    local extra_decl_json_file=""
    local revision="1"
    while [ $# -gt 0 ]; do
        case "$1" in
            (--noack) 
                ack=false
                shift
                ;;
            (--extra-decl-json)
                extra_decl_json_file="$2"
                shift 2
                ;;
            (--revision)
                revision="$2"
                shift 2
                ;;
            (*)
                break
                ;;
        esac
    done

    local dir="$1"
    local snap_path="$2"
    local snap_id="${3:-}"

    if [ -n "$snap_id" ]; then
        # unsquash the snap to get its name
        unsquashfs -d /tmp/snap-squashfs "$snap_path" meta/snap.yaml
        snap_name=$(gojq --yaml-input -r '.name' < /tmp/snap-squashfs/meta/snap.yaml)
        rm -rf /tmp/snap-squashfs

        cat >> /tmp/snap-decl.json << EOF
{
    "type": "snap-declaration",
    "snap-id": "${snap_id}",
    "publisher-id": "developer1",
    "snap-name": "${snap_name}"
}
EOF

        if [ -n "$extra_decl_json_file" ]; then
            # then we need to combine the extra snap declaration json with the one
            # we just wrote
            gojq -s '.[0] * .[1]' <(cat /tmp/snap-decl.json) <(cat "$extra_decl_json_file") > /tmp/snap-decl.json.tmp
            mv /tmp/snap-decl.json.tmp /tmp/snap-decl.json
        fi

        cat >> /tmp/snap-rev.json << EOF
{
    "type": "snap-revision",
    "snap-id": "${snap_id}",
    "snap-revision": "$revision"
}
EOF
    fi

    # NOTE: snap id and snap name are derived from snap file name
    local snap_decl_json_param=""
    local snap_rev_json_param=""
    if [ -n "$snap_id" ]; then
        snap_decl_json_param="--snap-decl-json=/tmp/snap-decl.json"
        snap_rev_json_param="--snap-rev-json=/tmp/snap-rev.json"
    fi
    local p_decl p_rev
    p_decl=$(fakestore new-snap-declaration --dir "$dir" "$snap_path" "$snap_decl_json_param")
    p_rev=$(fakestore new-snap-revision --dir "$dir" "$snap_path" "$snap_rev_json_param")

    if [ "$ack" = "true" ]; then
        snap ack "$p_decl"
        snap ack "$p_rev"
    fi

    cp -av "$snap_path" "$dir/"
    rm -f /tmp/snap-decl.json /tmp/snap-rev.json
}


make_component_installable(){
    local ack=true
    local component_rev="";
    local snap_rev="";
    local snap_id="";
    while [ $# -gt 0 ]; do
        case "$1" in
            (--component-revision)
                component_rev="$2"
                shift 2
                ;;
            (--snap-id)
                snap_id="$2"
                shift 2
                ;;
            (--snap-revision)
                snap_rev="$2"
                shift 2
                ;;
            (--noack)
                ack=false
                shift
                ;;
            (*)
                break
                ;;
        esac
    done

    if [ -z "${snap_id}" ]; then
        echo "snap-id must be provided"
        return 1
    fi

    if [ -z "${component_rev}" ]; then
        echo "component-revision must be provided"
        return 1
    fi

    if [ -z "${snap_rev}" ]; then
        echo "snap-revision must be provided"
        return 1
    fi

    local dir="$1"
    local path="$2"

    work=$(mktemp -d)

    cat > "/${work}/snap-resource-revision.json" << EOF
{
    "snap-id": "${snap_id}",
    "publisher-id": "developer1",
    "resource-revision": "${component_rev}"
}
EOF

    cat > "/${work}/snap-resource-pair.json" << EOF
{
    "snap-id": "${snap_id}",
    "publisher-id": "developer1",
    "resource-revision": "${component_rev}",
    "snap-revision": "${snap_rev}"
}
EOF

    resource_rev_assert=$(fakestore new-snap-resource-revision --dir "${dir}" "${path}" "/${work}/snap-resource-revision.json")
    resource_pair_assert=$(fakestore new-snap-resource-pair --dir "${dir}" "${path}" "/${work}/snap-resource-pair.json")

    if [ "${ack}" = "true" ]; then
        snap ack "${resource_rev_assert}"
        snap ack "${resource_pair_assert}"
    fi

    cp -av "${path}" "${dir}/"
    rm -rf "${work}"
}

setup_fake_store(){
    local top_dir=$1

    if [ -z "$top_dir" ]; then
        echo "store-state: the provided dir cannot be empty"
        return 1
    fi

    # before switching make sure we have a session macaroon, but keep it best
    # effort
    snap find test-snapd-tools || true
    mkdir -p "$top_dir/asserts"

    # debugging
    systemctl status fakestore || true
    
    # When a controlled store service is up
    # Create fakestore at the given port
    local https_proxy=${https_proxy:-}
    local http_proxy=${http_proxy:-}
    local port="11028"
    systemd-run --unit fakestore --setenv SNAPD_DEBUG=1 --setenv SNAPD_DEBUG_HTTP=7 --setenv SNAPPY_TESTING=1 --setenv SNAPPY_USE_STAGING_STORE="$SNAPPY_USE_STAGING_STORE" fakestore run --dir "$top_dir" --addr "localhost:$port" --https-proxy="${https_proxy}" --http-proxy="${http_proxy}" --assert-fallback

    # Configure snapd to use the controlled store
    _configure_store_backends "SNAPPY_FORCE_API_URL=http://localhost:$port" "SNAPPY_USE_STAGING_STORE=$SNAPPY_USE_STAGING_STORE"

    # Wait until fake store is ready
    if "$TESTSTOOLS"/network-state wait-listen-port "$port"; then
        return 0
    fi

    echo "store-state: fakestore service not started properly"
    ss -ntlp | grep "127.0.0.1:$port" || true
    "$TESTSTOOLS"/journal-state get-log -u fakestore || true
    systemctl status fakestore || true
    return 1
}

teardown_fake_store(){
    local top_dir=$1
    if [ ! -d "$top_dir" ]; then
        echo "store-state: the provided top dir does not exist \"$top_dir\""
    fi

    systemctl stop fakestore || true
    # when a unit fails, systemd may keep its status, resetting it allows to
    # start the unit again with a clean slate
    systemctl reset-failed fakestore || true

    if [ "$REMOTE_STORE" = "staging" ]; then
        setup_staging_store
    else
        systemctl stop snapd.service snapd.socket
        rm -rf "$STORE_CONFIG" "$top_dir"
        systemctl daemon-reload
        systemctl start snapd.socket
    fi
}

add_to_channel() {
    local BLOB_DIR=$1
    local FILENAME=$2
    local CHANNEL=$3
    SUM="$(snap info --verbose "$(realpath "${FILENAME}")" | sed '/^sha3-384: */{;s///;q;};d')"
    mkdir -p "${BLOB_DIR}/channels"
    echo "${CHANNEL}" >>"${BLOB_DIR}/channels/${SUM}"
}

main() {
    if [ $# -eq 0 ]; then
        show_help
        exit 0
    fi

    local subcommand=$1
    local action=
    while [ $# -gt 0 ]; do
        case "$1" in
            -h|--help)
                show_help
                exit 0
                ;;
            *)
                action=$(echo "$subcommand" | tr '-' '_')
                shift
                break
                ;;
        esac
    done

    if [ -z "$(declare -f "$action")" ]; then
        echo "store-state: no such command $subcommand" >&2
        show_help
        exit 1
    fi

    "$action" "$@"
}

main "$@"
