#!/usr/bin/env bash
set -Eeuo pipefail
umask 077

MODE="${1:---check}"

BASE="${ROUTER_OPS_BASE:-/opt/router-ops}"
PUBLIC_BASE="${ROUTER_PUBLIC_BASE_URL:-https://reports.secret-studio.ru}"
PUBLIC_BASE="${PUBLIC_BASE%/}"
REPOSITORY_URL="https://github.com/DChuvelev/wg-paid-vm121.git"
REPO_DIR="$BASE/state/source-repositories/wg-paid-vm121"
STATE_FILE="$BASE/state/current-vm121-git-source.env"
PUBLISH_TOOL="$BASE/bin/router-publish-report"

LOCK_DIR="$BASE/state/locks"
LOCK_FILE="$LOCK_DIR/vm121-git-source-publish.lock"

TMPDIR_RUN="$(mktemp -d /tmp/vm121-git-source-publish.XXXXXX)"
VM121_OUTPUT="$TMPDIR_RUN/vm121-publish.txt"

cleanup() {
    rm -rf "$TMPDIR_RUN"
}

trap cleanup EXIT INT TERM

log() {
    echo ">>> [vm121-source] $*"
}

stop() {
    echo "STOP=$*"
    exit 1
}

case "$MODE" in
    --check|--publish)
        ;;
    *)
        echo "Usage: $0 --check"
        echo "       $0 --publish"
        exit 2
        ;;
esac

mkdir -p \
    "$(dirname "$REPO_DIR")" \
    "$LOCK_DIR"

exec 9>"$LOCK_FILE"

if ! flock -n 9; then
    stop "ANOTHER_VM121_SOURCE_PUBLISH_IS_RUNNING"
fi

log "precheck"

command -v git >/dev/null 2>&1
command -v tar >/dev/null 2>&1
command -v gzip >/dev/null 2>&1
command -v sha256sum >/dev/null 2>&1
command -v python3 >/dev/null 2>&1
command -v flock >/dev/null 2>&1

test -x "$PUBLISH_TOOL"

echo "MODE=$MODE"
echo "REPOSITORY_URL=$REPOSITORY_URL"
echo "REPO_DIR=$REPO_DIR"

log "run VM121 git publisher through approved direct SSH"

if [ "$MODE" = "--publish" ]; then
    VM121_MODE="--publish"
else
    VM121_MODE="--check"
fi

set +e

ssh \
    -o BatchMode=yes \
    -o ConnectTimeout=15 \
    vm121 \
    "/usr/local/sbin/router-vm121-git-publish.sh \
        '$VM121_MODE'" \
    2>&1 |
tee "$VM121_OUTPUT"

VM121_RC=${PIPESTATUS[0]}

set -e

if [ "$VM121_RC" -ne 0 ]; then
    stop "VM121_GIT_PUBLISHER_FAILED_RC_$VM121_RC"
fi

VM121_RESULT="$(
    awk -F= '
        /^RESULT=/ {
            value=$2
        }
        END {
            print value
        }
    ' "$VM121_OUTPUT"
)"

VM121_COMMIT="$(
    awk -F= '
        /^GIT_COMMIT=/ {
            value=$2
        }
        END {
            print value
        }
    ' "$VM121_OUTPUT"
)"

if [ -z "$VM121_COMMIT" ]; then
    VM121_COMMIT="$(
        awk -F= '
            /^LOCAL_HEAD=/ {
                value=$2
            }
            END {
                print value
            }
        ' "$VM121_OUTPUT"
    )"
fi

VM121_REMOTE_COMMIT="$(
    awk -F= '
        /^REMOTE_COMMIT=/ {
            value=$2
        }
        /^REMOTE_HEAD=/ {
            if (value == "")
                value=$2
        }
        END {
            print value
        }
    ' "$VM121_OUTPUT"
)"

case "$VM121_COMMIT" in
    [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*)
        ;;
    *)
        stop "INVALID_VM121_COMMIT_$VM121_COMMIT"
        ;;
esac

echo
echo "VM121_RESULT=$VM121_RESULT"
echo "VM121_COMMIT=$VM121_COMMIT"
echo "VM121_REMOTE_COMMIT=$VM121_REMOTE_COMMIT"

if [ -n "$VM121_REMOTE_COMMIT" ] &&
   [ "$VM121_REMOTE_COMMIT" != "$VM121_COMMIT" ]
then
    stop "VM121_LOCAL_REMOTE_COMMIT_MISMATCH"
fi

log "clone or refresh repository on VM130"

if [ ! -d "$REPO_DIR/.git" ]; then
    rm -rf "$REPO_DIR"

    git clone \
        --branch main \
        "$REPOSITORY_URL" \
        "$REPO_DIR"
else
    CURRENT_ORIGIN="$(
        git -C "$REPO_DIR" remote get-url origin
    )"

    echo "CURRENT_ORIGIN=$CURRENT_ORIGIN"

    if [ "$CURRENT_ORIGIN" != "$REPOSITORY_URL" ]; then
        stop "UNEXPECTED_REPOSITORY_ORIGIN_$CURRENT_ORIGIN"
    fi

    git -C "$REPO_DIR" fetch \
        --prune \
        origin \
        main

    git -C "$REPO_DIR" checkout \
        -B main \
        origin/main

    git -C "$REPO_DIR" reset \
        --hard \
        origin/main

    git -C "$REPO_DIR" clean \
        -fdx
fi

VM130_COMMIT="$(
    git -C "$REPO_DIR" rev-parse HEAD
)"

VM130_TREE="$(
    git -C "$REPO_DIR" show -s --format=%T HEAD
)"

VM130_FILE_COUNT="$(
    git -C "$REPO_DIR" \
        ls-tree \
        -r \
        --full-tree \
        --name-only \
        HEAD |
    awk 'END {print NR + 0}'
)"

VM130_STATUS="$(
    git -C "$REPO_DIR" status --porcelain
)"

echo
echo "VM130_COMMIT=$VM130_COMMIT"
echo "VM130_TREE=$VM130_TREE"
echo "VM130_FILE_COUNT=$VM130_FILE_COUNT"
echo "VM130_STATUS=${VM130_STATUS:-CLEAN}"

test "$VM130_COMMIT" = "$VM121_COMMIT"
test -z "$VM130_STATUS"

log "verify required managed paths"

for REQUIRED_PATH in \
    etc/router-machine-git-source.conf \
    usr/local/sbin/router-machine-git-publish.sh \
    usr/local/sbin/router-vm121-git-publish.sh
do
    if [ ! -f "$REPO_DIR/$REQUIRED_PATH" ]; then
        stop "REQUIRED_PATH_MISSING_$REQUIRED_PATH"
    fi

    echo "REQUIRED_PATH_OK=$REQUIRED_PATH"
done

if [ "$MODE" = "--check" ]; then
    echo
    echo "RESULT=PASS_VM130_VM121_GIT_SOURCE_CHECK"
    echo "VM121_COMMIT=$VM121_COMMIT"
    echo "VM130_COMMIT=$VM130_COMMIT"
    echo "VM130_TREE=$VM130_TREE"
    echo "VM130_FILE_COUNT=$VM130_FILE_COUNT"
    echo "VM121_PUSH_PERFORMED=false"
    echo "ARCHIVE_CREATED=false"
    echo "PERMANENT_HTTPS_PUBLISHED=false"
    exit 0
fi

log "create archive and metadata from exact Git commit"

SHORT_COMMIT="${VM130_COMMIT:0:12}"
GENERATED_AT_UTC="$(date -u +%Y%m%d-%H%M%S)"

ARCHIVE_NAME="wg-paid-vm121-${SHORT_COMMIT}.tar.gz"
ARCHIVE="$TMPDIR_RUN/$ARCHIVE_NAME"
ARCHIVE_SHA_FILE="$TMPDIR_RUN/${ARCHIVE_NAME}.sha256"
ARCHIVE_B64_NAME="${ARCHIVE_NAME}.b64.txt"
ARCHIVE_B64="$TMPDIR_RUN/$ARCHIVE_B64_NAME"
MANIFEST="$TMPDIR_RUN/git-tree-manifest.txt"
PATHS="$TMPDIR_RUN/managed-paths.txt"
COMMIT_INFO="$TMPDIR_RUN/git-commit.txt"
FACTS="$TMPDIR_RUN/facts.json"

git -C "$REPO_DIR" archive \
    --format=tar.gz \
    --prefix="wg-paid-vm121-${SHORT_COMMIT}/" \
    "$VM130_COMMIT" \
    > "$ARCHIVE"

sha256sum "$ARCHIVE" \
    > "$ARCHIVE_SHA_FILE"

python3 - "$ARCHIVE" "$ARCHIVE_B64" <<'PY'
import base64
import pathlib
import sys
import textwrap

source = pathlib.Path(sys.argv[1])
output = pathlib.Path(sys.argv[2])

encoded = base64.b64encode(source.read_bytes()).decode("ascii")

with output.open("w", encoding="ascii", newline="\n") as stream:
    for line in textwrap.wrap(encoded, 76):
        stream.write(line)
        stream.write("\n")
PY

git -C "$REPO_DIR" \
    ls-tree \
    -r \
    --full-tree \
    "$VM130_COMMIT" \
    > "$MANIFEST"

git -C "$REPO_DIR" \
    ls-tree \
    -r \
    --full-tree \
    --name-only \
    "$VM130_COMMIT" \
    > "$PATHS"

git -C "$REPO_DIR" \
    show \
    -s \
    --format=fuller \
    "$VM130_COMMIT" \
    > "$COMMIT_INFO"

ARCHIVE_SHA256="$(
    awk '{print $1}' "$ARCHIVE_SHA_FILE"
)"

python3 - \
    "$FACTS" \
    "$GENERATED_AT_UTC" \
    "$REPOSITORY_URL" \
    "$VM130_COMMIT" \
    "$VM130_TREE" \
    "$VM130_FILE_COUNT" \
    "$ARCHIVE_NAME" \
    "$ARCHIVE_SHA256" <<'PY'
import json
import sys

(
    output,
    generated_at,
    repository,
    commit,
    tree,
    file_count,
    archive,
    archive_sha256,
) = sys.argv[1:]

facts = {
    "result": "PASS_VM121_GIT_SOURCE_ARCHIVE",
    "generated_at_utc": generated_at,
    "repository": repository,
    "branch": "main",
    "commit": commit,
    "tree": tree,
    "file_count": int(file_count),
    "archive": archive,
    "archive_sha256": archive_sha256,
    "source_contract": "VM121 live worktree -> Git commit -> GitHub main -> VM130 checkout -> Git archive",
}

with open(output, "w", encoding="utf-8") as stream:
    json.dump(facts, stream, ensure_ascii=False, indent=2)
    stream.write("\n")
PY

python3 - "$FACTS" "$ARCHIVE_B64_NAME" <<'PY'
import json
import pathlib
import sys

facts_path = pathlib.Path(sys.argv[1])
archive_b64_name = sys.argv[2]

facts = json.loads(facts_path.read_text(encoding="utf-8"))
facts["archive_base64_transport"] = archive_b64_name
facts["archive_base64_encoding"] = "base64"
facts["archive_base64_decodes_to"] = facts["archive"]

facts_path.write_text(
    json.dumps(facts, ensure_ascii=False, indent=2) + "\n",
    encoding="utf-8",
)
PY

echo "ARCHIVE=$ARCHIVE"
echo "ARCHIVE_SHA256=$ARCHIVE_SHA256"
echo "ARCHIVE_BASE64=$ARCHIVE_B64"
echo "MANIFEST=$MANIFEST"
echo "PATHS=$PATHS"
echo "FACTS=$FACTS"

log "publish atomically into latest/ and archive prior version into old/"

LABEL="vm121_git_source_${SHORT_COMMIT}"

PUBLISH_OUTPUT="$(
    "$PUBLISH_TOOL" \
        "$LABEL" \
        "$ARCHIVE" \
        "$ARCHIVE_SHA_FILE" \
        "$ARCHIVE_B64" \
        "$MANIFEST" \
        "$PATHS" \
        "$COMMIT_INFO" \
        "$FACTS" \
        "$VM121_OUTPUT"
)"

printf '%s\n' "$PUBLISH_OUTPUT"

read_publish_field() {
    local key="$1"

    printf '%s\n' "$PUBLISH_OUTPUT" |
    awk -F= -v key="$key" '
        $1 == key {
            print substr($0, index($0, "=") + 1)
        }
    ' |
    tail -n 1
}

PUBLISHED_DIR="$(read_publish_field PUBLISHED_DIR)"
PUBLISHED_RELATIVE_PATH="$(read_publish_field PUBLISHED_RELATIVE_PATH)"
PUBLIC_OBJECT_KIND="$(read_publish_field PUBLIC_OBJECT_KIND)"
LATEST_INDEX_CONTAINS_FOLDER="$(read_publish_field LATEST_INDEX_CONTAINS_FOLDER)"
HTTP_POSTCHECK="$(read_publish_field HTTP_POSTCHECK)"
SOURCE_URL="$(read_publish_field VM121_PUBLIC_URL)"

EXPECTED_KIND="vm121"
EXPECTED_SOURCE_URL="${PUBLIC_BASE}/${PUBLISHED_RELATIVE_PATH%/}/"

[ -n "$PUBLISHED_DIR" ]
[ -d "$PUBLISHED_DIR" ]
[ -n "$PUBLISHED_RELATIVE_PATH" ]
[ "$PUBLIC_OBJECT_KIND" = "$EXPECTED_KIND" ]
[ "$LATEST_INDEX_CONTAINS_FOLDER" = "true" ]
[ "$HTTP_POSTCHECK" = "PASS" ]

case "$PUBLISHED_RELATIVE_PATH" in
    latest/*)
        ;;
    *)
        stop "INVALID_PUBLISHED_RELATIVE_PATH_$PUBLISHED_RELATIVE_PATH"
        ;;
esac

case "$PUBLISHED_DIR" in
    "$BASE"/public/r/*/latest/*)
        ;;
    *)
        stop "INVALID_PUBLISHED_DIR_$PUBLISHED_DIR"
        ;;
esac

[ "$SOURCE_URL" = "$EXPECTED_SOURCE_URL" ] ||
    stop "INVALID_PERMANENT_SOURCE_URL_$SOURCE_URL"

[ -f "$PUBLISHED_DIR/$ARCHIVE_NAME" ]
[ -f "$PUBLISHED_DIR/$ARCHIVE_B64_NAME" ]
[ -f "$PUBLISHED_DIR/facts.json" ]
[ -f "$PUBLISHED_DIR/git-tree-manifest.txt" ]
[ "$(sha256sum "$PUBLISHED_DIR/$ARCHIVE_NAME" | awk '{print $1}')" = "$ARCHIVE_SHA256" ]

ARCHIVE_URL="${SOURCE_URL}${ARCHIVE_NAME}"
ARCHIVE_B64_URL="${SOURCE_URL}${ARCHIVE_B64_NAME}"
FACTS_URL="${SOURCE_URL}facts.json"
MANIFEST_URL="${SOURCE_URL}git-tree-manifest.txt"

log "save current source archive state"

STATE_TMP="${STATE_FILE}.tmp.$$"

{
    echo "VM121_GIT_SOURCE_UPDATED_AT_UTC=$GENERATED_AT_UTC"
    echo "VM121_GIT_SOURCE_REPOSITORY=$REPOSITORY_URL"
    echo "VM121_GIT_SOURCE_BRANCH=main"
    echo "VM121_GIT_SOURCE_COMMIT=$VM130_COMMIT"
    echo "VM121_GIT_SOURCE_TREE=$VM130_TREE"
    echo "VM121_GIT_SOURCE_FILE_COUNT=$VM130_FILE_COUNT"
    echo "VM121_GIT_SOURCE_PUBLIC_DIR=$PUBLISHED_DIR"
    echo "VM121_GIT_SOURCE_URL=$SOURCE_URL"
    echo "VM121_GIT_SOURCE_ARCHIVE_URL=$ARCHIVE_URL"
    echo "VM121_GIT_SOURCE_ARCHIVE_B64_URL=$ARCHIVE_B64_URL"
    echo "VM121_GIT_SOURCE_FACTS_URL=$FACTS_URL"
    echo "VM121_GIT_SOURCE_MANIFEST_URL=$MANIFEST_URL"
    echo "VM121_GIT_SOURCE_ARCHIVE_SHA256=$ARCHIVE_SHA256"
} > "$STATE_TMP"

chmod 600 "$STATE_TMP"
mv "$STATE_TMP" "$STATE_FILE"

echo
echo "RESULT=PASS_VM130_VM121_GIT_SOURCE_PUBLISH"
echo "VM121_GIT_SOURCE_COMMIT=$VM130_COMMIT"
echo "VM121_GIT_SOURCE_TREE=$VM130_TREE"
echo "VM121_GIT_SOURCE_FILE_COUNT=$VM130_FILE_COUNT"
echo "VM121_GIT_SOURCE_URL=$SOURCE_URL"
echo "VM121_GIT_SOURCE_ARCHIVE_URL=$ARCHIVE_URL"
echo "VM121_GIT_SOURCE_ARCHIVE_B64_URL=$ARCHIVE_B64_URL"
echo "VM121_GIT_SOURCE_FACTS_URL=$FACTS_URL"
echo "VM121_GIT_SOURCE_MANIFEST_URL=$MANIFEST_URL"
echo "VM121_GIT_SOURCE_ARCHIVE_SHA256=$ARCHIVE_SHA256"
echo "VM121_PUSH_PERFORMED=true"
echo "ARCHIVE_CREATED=true"
echo "PERMANENT_HTTPS_PUBLISHED=true"
