#!/bin/bash

set -u

CLAIM_TTL=300
TRUSTED_INSTALL_NAME="ipz-remote-gate-hooks"
TRUST_DEPTH=5
TRUST_REF="refs/remotes/origin/master"
GATE_PRECOMMIT_SRC=".dev-config/hooks/pre-commit"
GATE_REFERENCE_SRC=".dev-config/hooks/reference-transaction"
GATE_HELPER_SRC=".dev-config/bin/ipz-remote-php-gate"

# Print the blob OIDs of $1 across the last TRUST_DEPTH landed versions on TRUST_REF.
trusted_blobs_for_path() {
    local path=$1 commit blob
    git -C "$repo_root" rev-list -n "$TRUST_DEPTH" "$TRUST_REF" -- "$path" 2>/dev/null |
    while read -r commit; do
        blob=$(git -C "$repo_root" rev-parse -q --verify "$commit:$path" 2>/dev/null) || continue
        printf '%s\n' "$blob"
    done
}

# Installed file $1 must byte-match some landed version of source path $2.
installed_blob_is_trusted() {
    local active=$1 tracked=$2 active_oid blob matched=1
    active_oid=$(git -C "$repo_root" hash-object --no-filters "$active" 2>/dev/null) || return 1
    while read -r blob; do
        [ -n "$blob" ] && [ "$active_oid" = "$blob" ] && matched=0
    done < <(trusted_blobs_for_path "$tracked")
    return "$matched"
}

fail() {
    printf 'IPZ reference transaction: %s\n' "$*" >&2
    return 1
}

is_hex() {
    case "$1" in (*[!0-9a-f]*|'') return 1 ;; esac
    [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]
}

is_parent_vector() {
    local vector=$1 expected_head=$2 parent
    local -a parents
    case "$vector" in ,*|*,|*,,*) return 1 ;; esac
    IFS=, read -r -a parents <<<"$vector"
    [ ${#parents[@]} -gt 0 ] && [ ${#parents[@]} -le 32 ] || return 1
    [ "${parents[0]}" = "$expected_head" ] || return 1
    for parent in "${parents[@]}"; do is_hex "$parent" || return 1; done
}

hash_identity() {
    local digest
    digest=$(sha256sum <<<"$1") || return 1
    printf '%s\n' "${digest%% *}"
}

burn_file() {
    local path=$1 burned
    [ ! -L "$path" ] || return 1
    [ -e "$path" ] || return 0
    burned="$path.burned.$$.$RANDOM"
    mv "$path" "$burned" || return 1
    rm -f "$burned"
}

load_claim() {
    local path=$1 require_new=$2 key value lines=0
    local seen_version=0 seen_head=0 seen_tree=0 seen_parents=0 seen_contract=0 seen_hook=0 seen_nonce=0
    local seen_repo=0 seen_worktree=0 seen_created=0 seen_ref_state=0 seen_ref=0 seen_new=0
    CLAIM_VERSION="" CLAIM_HEAD="" CLAIM_TREE="" CLAIM_PARENTS="" CLAIM_CONTRACT="" CLAIM_HOOK="" CLAIM_NONCE=""
    CLAIM_REPO="" CLAIM_WORKTREE="" CLAIM_CREATED="" CLAIM_REF_STATE="" CLAIM_REF="" CLAIM_NEW=""

    [ ! -L "$path" ] && [ -f "$path" ] || return 1
    [ "$(stat -c %u "$path" 2>/dev/null)" = "$(id -u)" ] || return 1
    [ "$(stat -c %a "$path" 2>/dev/null)" = 600 ] || return 1
    while IFS='=' read -r key value; do
        lines=$((lines + 1))
        [ "$lines" -le 13 ] || return 1
        case "$key" in
            version) [ "$seen_version" -eq 0 ] || return 1; CLAIM_VERSION=$value; seen_version=1 ;;
            head) [ "$seen_head" -eq 0 ] || return 1; CLAIM_HEAD=$value; seen_head=1 ;;
            tree) [ "$seen_tree" -eq 0 ] || return 1; CLAIM_TREE=$value; seen_tree=1 ;;
            parents) [ "$seen_parents" -eq 0 ] || return 1; CLAIM_PARENTS=$value; seen_parents=1 ;;
            contract) [ "$seen_contract" -eq 0 ] || return 1; CLAIM_CONTRACT=$value; seen_contract=1 ;;
            hook) [ "$seen_hook" -eq 0 ] || return 1; CLAIM_HOOK=$value; seen_hook=1 ;;
            nonce) [ "$seen_nonce" -eq 0 ] || return 1; CLAIM_NONCE=$value; seen_nonce=1 ;;
            repo) [ "$seen_repo" -eq 0 ] || return 1; CLAIM_REPO=$value; seen_repo=1 ;;
            worktree) [ "$seen_worktree" -eq 0 ] || return 1; CLAIM_WORKTREE=$value; seen_worktree=1 ;;
            created) [ "$seen_created" -eq 0 ] || return 1; CLAIM_CREATED=$value; seen_created=1 ;;
            ref_state) [ "$seen_ref_state" -eq 0 ] || return 1; CLAIM_REF_STATE=$value; seen_ref_state=1 ;;
            ref) [ "$seen_ref" -eq 0 ] || return 1; CLAIM_REF=$value; seen_ref=1 ;;
            new) [ "$seen_new" -eq 0 ] || return 1; CLAIM_NEW=$value; seen_new=1 ;;
            *) return 1 ;;
        esac
    done <"$path"

    [ "$CLAIM_VERSION" = 2 ] || return 1
    is_hex "$CLAIM_HEAD" && is_hex "$CLAIM_TREE" && is_parent_vector "$CLAIM_PARENTS" "$CLAIM_HEAD" || return 1
    [ "$seen_parents" -eq 1 ] || return 1
    [ "${#CLAIM_CONTRACT}" -eq 64 ] && is_hex "$CLAIM_CONTRACT" || return 1
    [ "$seen_hook" -eq 1 ] && [ "${#CLAIM_HOOK}" -eq 64 ] && is_hex "$CLAIM_HOOK" || return 1
    [ "${#CLAIM_NONCE}" -eq 64 ] && is_hex "$CLAIM_NONCE" || return 1
    [ "${#CLAIM_REPO}" -eq 64 ] && is_hex "$CLAIM_REPO" || return 1
    [ "${#CLAIM_WORKTREE}" -eq 64 ] && is_hex "$CLAIM_WORKTREE" || return 1
    case "$CLAIM_CREATED" in (*[!0-9]*|'') return 1 ;; esac
    case "$CLAIM_CREATED" in 0|[1-9]*) ;; *) return 1 ;; esac
    [ "${#CLAIM_CREATED}" -le 12 ] || return 1
    case "$CLAIM_REF_STATE:$CLAIM_REF" in symbolic:refs/*|detached:HEAD) ;; *) return 1 ;; esac
    if [ "$require_new" -eq 1 ]; then
        [ "$seen_new" -eq 1 ] && is_hex "$CLAIM_NEW" || return 1
    else
        [ "$seen_new" -eq 0 ] || return 1
    fi
}

active_reference_hook_hash() {
    local source_path hooks_path expected_path version_id path digest rc
    source_path=${BASH_SOURCE[0]}
    [ ! -L "$source_path" ] && [ -f "$source_path" ] && [ -x "$source_path" ] || return 1
    source_path=$(realpath "$source_path" 2>/dev/null) || return 1

    hooks_path=$(git config --path --get core.hooksPath 2>/dev/null)
    rc=$?
    [ "$rc" -eq 0 ] && [ -n "$hooks_path" ] || return 1
    if [ "${hooks_path#/}" = "$hooks_path" ]; then hooks_path="$repo_root/$hooks_path"; fi
    [ ! -L "$hooks_path" ] || return 1
    hooks_path=$(realpath "$hooks_path" 2>/dev/null) || return 1
    expected_path="$common_dir/$TRUSTED_INSTALL_NAME"
    [ ! -L "$expected_path" ] && [ -d "$expected_path" ] || return 1
    [ "$(stat -c %u "$expected_path" 2>/dev/null)" = "$(id -u)" ] || return 1
    [ "$(stat -c %a "$expected_path" 2>/dev/null)" = 700 ] || return 1
    if [ "$hooks_path" = "$expected_path" ]; then
        [ "$(stat -c %a "$hooks_path" 2>/dev/null)" = 700 ] || return 1
    else
        version_id=${hooks_path##*/}
        [ "$(dirname "$hooks_path")" = "$expected_path" ] || return 1
        [[ "$version_id" =~ ^[0-9a-f]{12}$ ]] || return 1
        [ "$(stat -c %a "$hooks_path" 2>/dev/null)" = 500 ] || return 1
    fi
    [ -d "$hooks_path" ] || return 1
    [ "$(stat -c %u "$hooks_path" 2>/dev/null)" = "$(id -u)" ] || return 1
    ACTIVE_PRECOMMIT="$hooks_path/pre-commit"
    ACTIVE_HELPER="$hooks_path/ipz-remote-php-gate"
    ACTIVE_REFERENCE="$hooks_path/reference-transaction"
    for path in "$ACTIVE_PRECOMMIT" "$ACTIVE_HELPER" "$ACTIVE_REFERENCE"; do
        [ -f "$path" ] && [ ! -L "$path" ] && [ -x "$path" ] || return 1
        [ "$(realpath "$path" 2>/dev/null)" = "$path" ] || return 1
        [ "$(stat -c %u "$path" 2>/dev/null)" = "$(id -u)" ] || return 1
        [ "$(stat -c %a "$path" 2>/dev/null)" = 500 ] || return 1
    done
    [ "$source_path" = "$ACTIVE_REFERENCE" ] || return 1
    digest=$(sha256sum "$source_path" 2>/dev/null) || return 1
    ACTIVE_REFERENCE_HASH=${digest%% *}
}

claim_is_current() {
    local now age active_contract
    [ "$CLAIM_REPO" = "$repo_identity" ] && [ "$CLAIM_WORKTREE" = "$worktree_identity" ] || return 1
    active_reference_hook_hash || return 1
    [ "$ACTIVE_REFERENCE_HASH" = "$CLAIM_HOOK" ] || return 1
    installed_blob_is_trusted "$ACTIVE_PRECOMMIT" "$GATE_PRECOMMIT_SRC" || return 1
    installed_blob_is_trusted "$ACTIVE_HELPER" "$GATE_HELPER_SRC" || return 1
    installed_blob_is_trusted "$ACTIVE_REFERENCE" "$GATE_REFERENCE_SRC" || return 1
    active_contract=$(sha256sum "$ACTIVE_HELPER" 2>/dev/null) || return 1
    [ "${active_contract%% *}" = "$CLAIM_CONTRACT" ] || return 1
    now=$(date +%s) || return 1
    [ "$CLAIM_CREATED" -le $((now + 10)) ] || return 1
    age=$((now - CLAIM_CREATED))
    [ "$age" -le "$CLAIM_TTL" ]
}

read_updates() {
    local old new ref extra
    UPDATE_OLD=() UPDATE_NEW=() UPDATE_REF=()
    while IFS=' ' read -r old new ref extra; do
        [ -n "$old" ] || continue
        [ -n "$new" ] && [ -n "$ref" ] && [ -z "${extra:-}" ] || return 2
        [ ${#UPDATE_REF[@]} -lt 32 ] || return 2
        UPDATE_OLD+=("$old") UPDATE_NEW+=("$new") UPDATE_REF+=("$ref")
    done
    [ ${#UPDATE_REF[@]} -gt 0 ] || return 1
}

validate_update() {
    local parent_line i
    local -a commit_parts expected_parents
    [ ${#UPDATE_REF[@]} -eq 1 ] || return 1
    [ "${UPDATE_REF[0]}" = "$CLAIM_REF" ] || return 1
    MATCH_OLD=${UPDATE_OLD[0]}
    MATCH_NEW=${UPDATE_NEW[0]}
    [ "$MATCH_OLD" = "$CLAIM_HEAD" ] && [ "$MATCH_NEW" != "$CLAIM_HEAD" ] || return 1
    [ "$(git cat-file -t "$MATCH_NEW" 2>/dev/null)" = commit ] || return 1
    [ "$(git rev-parse "$MATCH_NEW^{tree}" 2>/dev/null)" = "$CLAIM_TREE" ] || return 1
    parent_line=$(git rev-list --parents -n 1 "$MATCH_NEW" 2>/dev/null) || return 1
    read -r -a commit_parts <<<"$parent_line"
    IFS=, read -r -a expected_parents <<<"$CLAIM_PARENTS"
    [ ${#commit_parts[@]} -eq $(( ${#expected_parents[@]} + 1 )) ] \
        && [ "${commit_parts[0]}" = "$MATCH_NEW" ] || return 1
    for ((i = 0; i < ${#expected_parents[@]}; i++)); do
        [ "${commit_parts[$((i + 1))]}" = "${expected_parents[$i]}" ] || return 1
    done
}

update_requires_claim() {
    local checked_ref checked_head rc i
    if checked_ref=$(git symbolic-ref -q HEAD 2>/dev/null); then
        :
    else
        rc=$?
        [ "$rc" -eq 1 ] || return 1
        checked_ref=HEAD
    fi
    checked_head=$(git rev-parse --verify HEAD 2>/dev/null) || return 1
    for ((i = 0; i < ${#UPDATE_REF[@]}; i++)); do
        [ "${UPDATE_REF[$i]}" = "$checked_ref" ] || continue
        [ "${UPDATE_OLD[$i]}" = "$checked_head" ] || continue
        [ "${UPDATE_NEW[$i]}" != "$checked_head" ] || continue
        [ "$(git cat-file -t "${UPDATE_NEW[$i]}" 2>/dev/null)" = commit ] && return 0
    done
    return 1
}

transaction_targets_loaded_claim() {
    local i
    for ((i = 0; i < ${#UPDATE_REF[@]}; i++)); do
        [ "${UPDATE_REF[$i]}" = "$CLAIM_REF" ] || continue
        [ "${UPDATE_OLD[$i]}" = "$CLAIM_HEAD" ] || continue
        [ "${UPDATE_NEW[$i]}" != "$CLAIM_HEAD" ] || continue
        return 0
    done
    return 1
}

phase=${1-}
[ "$phase" = prepared ] || [ "$phase" = committed ] || [ "$phase" = aborted ] || exit 0

git_dir=$(git rev-parse --absolute-git-dir 2>/dev/null) || { fail 'cannot resolve worktree Git directory'; exit 1; }
repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || { fail 'cannot resolve repository root'; exit 1; }
common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || { fail 'cannot resolve common Git directory'; exit 1; }
common_dir=$(realpath "$common_dir" 2>/dev/null) || { fail 'cannot resolve common Git directory'; exit 1; }
git_dir=$(realpath "$git_dir" 2>/dev/null) || { fail 'cannot resolve worktree Git directory'; exit 1; }
repo_root=$(realpath "$repo_root" 2>/dev/null) || { fail 'cannot resolve repository root'; exit 1; }
repo_identity=$(hash_identity "$common_dir:$repo_root") || { fail 'cannot bind repository identity'; exit 1; }
worktree_identity=$(hash_identity "$git_dir:$repo_root") || { fail 'cannot bind worktree identity'; exit 1; }
claim="$git_dir/ipz-remote-gate.claim"
inflight="$git_dir/ipz-remote-gate.inflight"
lock="$git_dir/ipz-remote-gate.claim.lock"
umask 077
exec 9>"$lock" || { fail 'cannot open claim lock'; exit 1; }
flock -w 30 9 || { fail 'cannot acquire claim lock'; exit 1; }

read_updates
update_rc=$?
[ "$update_rc" -ne 2 ] || { fail 'cannot read reference transaction'; exit 1; }

if [ "$phase" = prepared ]; then
    if [ ! -e "$claim" ] && [ ! -L "$claim" ]; then
        if [ -e "$inflight" ] || [ -L "$inflight" ]; then
            if [ "$update_rc" -eq 0 ]; then
                load_claim "$inflight" 1 || { fail 'malformed or unsafe in-flight claim'; exit 1; }
                transaction_targets_loaded_claim || exit 0
            else
                exit 0
            fi
            fail 'replayed or unfinished claim'
            exit 1
        fi
        if [ "$update_rc" -eq 0 ] && update_requires_claim; then
            fail 'normal checked-out-ref commit is missing its required claim'
            exit 1
        fi
        exit 0
    fi
    [ "$update_rc" -eq 0 ] || exit 0
    load_claim "$claim" 0 || { fail 'malformed or unsafe claim'; exit 1; }
    transaction_targets_loaded_claim || exit 0
    if [ -e "$inflight" ] || [ -L "$inflight" ]; then fail 'duplicate claim state'; exit 1; fi
    if ! claim_is_current; then
        burn_file "$claim" || { fail 'cannot burn stale claim'; exit 1; }
        fail 'stale or foreign claim'
        exit 1
    fi
    if ! validate_update; then
        burn_file "$claim" || { fail 'cannot burn rejected claim'; exit 1; }
        fail 'proposed reference transaction does not match the tested tree'
        exit 1
    fi
    mv "$claim" "$inflight" || { fail 'cannot consume claim'; exit 1; }
    printf 'new=%s\n' "$MATCH_NEW" >>"$inflight" || { burn_file "$inflight"; fail 'cannot bind proposed commit'; exit 1; }
    exit 0
fi

if [ "$phase" = aborted ]; then
    if { [ -e "$inflight" ] || [ -L "$inflight" ]; } && { [ -e "$claim" ] || [ -L "$claim" ]; }; then
        [ "$update_rc" -eq 0 ] || exit 0
        load_claim "$claim" 0 || { fail 'malformed or unsafe pending claim'; exit 1; }
        transaction_targets_loaded_claim || exit 0
        burn_file "$inflight" && burn_file "$claim" || { fail 'cannot burn aborted duplicate claim state'; exit 1; }
        exit 0
    fi
    if [ -e "$inflight" ] || [ -L "$inflight" ]; then
        [ "$update_rc" -eq 0 ] || exit 0
        load_claim "$inflight" 1 || { fail 'malformed or unsafe in-flight claim'; exit 1; }
        transaction_targets_loaded_claim || exit 0
        burn_file "$inflight" || { fail 'cannot burn aborted in-flight claim'; exit 1; }
        exit 0
    fi
    if [ -e "$claim" ] || [ -L "$claim" ]; then
        [ "$update_rc" -eq 0 ] || exit 0
        load_claim "$claim" 0 || { fail 'malformed or unsafe claim'; exit 1; }
        transaction_targets_loaded_claim || exit 0
        burn_file "$claim" || { fail 'cannot burn aborted claim'; exit 1; }
    fi
    exit 0
fi

if [ -e "$inflight" ] || [ -L "$inflight" ]; then
    [ "$update_rc" -eq 0 ] || exit 0
    if ! load_claim "$inflight" 1; then
        fail 'malformed or unsafe in-flight claim'
        exit 1
    fi
    transaction_targets_loaded_claim || exit 0
    if ! claim_is_current; then
        burn_file "$inflight" || true
        fail 'malformed, stale, or foreign in-flight claim'
        exit 1
    fi
    if ! validate_update || [ "$MATCH_NEW" != "$CLAIM_NEW" ]; then
        burn_file "$inflight" || true
        fail 'committed transaction does not match the prepared claim'
        exit 1
    fi
    burn_file "$inflight" || { fail 'cannot finalize in-flight claim'; exit 1; }
    exit 0
fi

if [ -e "$claim" ] || [ -L "$claim" ]; then
    [ "$update_rc" -eq 0 ] || exit 0
    load_claim "$claim" 0 || { fail 'malformed or unsafe claim'; exit 1; }
    transaction_targets_loaded_claim || exit 0
    burn_file "$claim" || { fail 'cannot burn unprepared claim'; exit 1; }
    fail 'committed transaction had an unprepared claim'
    exit 1
fi
exit 0
