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

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

BASE="/opt/router-ops"
REPOSITORY_URL="https://github.com/DChuvelev/wg-paid-vm101.git"
REPO_DIR="$BASE/state/source-repositories/wg-paid-vm101"
STATE_FILE="$BASE/state/current-vm101-git-source.env"
TUNNEL_ENV="$BASE/state/current-quick-tunnel.env"
TOKEN_FILE="$BASE/public/.router-public-token"
PUBLISH_TOOL="$BASE/bin/router-publish-report"

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

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

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

trap cleanup EXIT INT TERM

log() {
    echo ">>> [vm101-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_VM101_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"
test -f "$TOKEN_FILE"
test -f "$TUNNEL_ENV"

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

log "run VM101 git publisher through approved nested SSH"

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

set +e

ssh \
    -o BatchMode=yes \
    -o ConnectTimeout=15 \
    pve-mgts \
    "ssh \
        -n \
        -o BatchMode=yes \
        -o ConnectTimeout=15 \
        -o StrictHostKeyChecking=accept-new \
        -i /root/.ssh/pve_to_openwrt_mgts_ed25519 \
        root@10.71.100.2 \
        /usr/local/sbin/router-vm101-git-publish.sh \
        $VM101_MODE" \
    > "$VM101_OUTPUT" 2>&1

VM101_RC=$?

set -e

cat "$VM101_OUTPUT"

echo
echo "VM101_PUBLISHER_RC=$VM101_RC"

if [ "$VM101_RC" -ne 0 ]; then
    stop "VM101_GIT_PUBLISHER_FAILED_RC_$VM101_RC"
fi

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

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

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

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

case "$VM101_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_VM101_COMMIT_$VM101_COMMIT"
        ;;
esac

echo
echo "VM101_RESULT=$VM101_RESULT"
echo "VM101_COMMIT=$VM101_COMMIT"
echo "VM101_REMOTE_COMMIT=$VM101_REMOTE_COMMIT"

if [ -n "$VM101_REMOTE_COMMIT" ] &&
   [ "$VM101_REMOTE_COMMIT" != "$VM101_COMMIT" ]
then
    stop "VM101_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" = "$VM101_COMMIT"
test -z "$VM130_STATUS"

log "verify required managed paths"

for REQUIRED_PATH in \
    etc/crontabs/root \
    usr/local/sbin/router-vm101-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_VM101_GIT_SOURCE_CHECK"
    echo "VM101_COMMIT=$VM101_COMMIT"
    echo "VM130_COMMIT=$VM130_COMMIT"
    echo "VM130_TREE=$VM130_TREE"
    echo "VM130_FILE_COUNT=$VM130_FILE_COUNT"
    echo "VM101_PUSH_PERFORMED=false"
    echo "ARCHIVE_CREATED=false"
    echo "CLOUDFLARE_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-vm101-${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-vm101-${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_VM101_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": "VM101 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="vm101_git_source_${SHORT_COMMIT}"

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

printf '%s\n' "$PUBLISH_OUTPUT"

PUBLISHED_DIR="$(
    printf '%s\n' "$PUBLISH_OUTPUT" |
    awk -F= '
        /^PUBLISHED_DIR=/ {
            print $2
        }
    '
)"

test -n "$PUBLISHED_DIR"
test -d "$PUBLISHED_DIR"

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

PUBLIC_TOKEN="$(cat "$TOKEN_FILE")"

QUICK_TUNNEL_URL="$(
    awk -F= '
        $1 == "QUICK_TUNNEL_URL" {
            print substr($0, index($0, "=") + 1)
        }
    ' "$TUNNEL_ENV"
)"

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

test -n "$QUICK_TUNNEL_URL"

SOURCE_URL="${QUICK_TUNNEL_URL%/}/r/${PUBLIC_TOKEN}/${PUBLISHED_RELATIVE_PATH%/}/"
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 "VM101_GIT_SOURCE_UPDATED_AT_UTC=$GENERATED_AT_UTC"
    echo "VM101_GIT_SOURCE_REPOSITORY=$REPOSITORY_URL"
    echo "VM101_GIT_SOURCE_BRANCH=main"
    echo "VM101_GIT_SOURCE_COMMIT=$VM130_COMMIT"
    echo "VM101_GIT_SOURCE_TREE=$VM130_TREE"
    echo "VM101_GIT_SOURCE_FILE_COUNT=$VM130_FILE_COUNT"
    echo "VM101_GIT_SOURCE_PUBLIC_DIR=$PUBLISHED_DIR"
    echo "VM101_GIT_SOURCE_URL=$SOURCE_URL"
    echo "VM101_GIT_SOURCE_ARCHIVE_URL=$ARCHIVE_URL"
    echo "VM101_GIT_SOURCE_ARCHIVE_B64_URL=$ARCHIVE_B64_URL"
    echo "VM101_GIT_SOURCE_FACTS_URL=$FACTS_URL"
    echo "VM101_GIT_SOURCE_MANIFEST_URL=$MANIFEST_URL"
    echo "VM101_GIT_SOURCE_ARCHIVE_SHA256=$ARCHIVE_SHA256"
} > "$STATE_TMP"

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

echo
echo "RESULT=PASS_VM130_VM101_GIT_SOURCE_PUBLISH"
echo "VM101_GIT_SOURCE_COMMIT=$VM130_COMMIT"
echo "VM101_GIT_SOURCE_TREE=$VM130_TREE"
echo "VM101_GIT_SOURCE_FILE_COUNT=$VM130_FILE_COUNT"
echo "VM101_GIT_SOURCE_URL=$SOURCE_URL"
echo "VM101_GIT_SOURCE_ARCHIVE_URL=$ARCHIVE_URL"
echo "VM101_GIT_SOURCE_ARCHIVE_B64_URL=$ARCHIVE_B64_URL"
echo "VM101_GIT_SOURCE_FACTS_URL=$FACTS_URL"
echo "VM101_GIT_SOURCE_MANIFEST_URL=$MANIFEST_URL"
echo "VM101_GIT_SOURCE_ARCHIVE_SHA256=$ARCHIVE_SHA256"
echo "VM101_PUSH_PERFORMED=true"
echo "ARCHIVE_CREATED=true"
echo "CLOUDFLARE_PUBLISHED=true"
