#!/usr/bin/env bash
# Wrap Zyryn's verified portable Linux tarball in one executable .run file.
set -euo pipefail

readonly SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
readonly TEMPLATE="$SCRIPT_DIR/linux_run_stub.sh.in"
readonly PAYLOAD_MARKER='__ZYRYN_PORTABLE_PAYLOAD_V1__'
readonly WRAPPER_FORMAT='1'

ARCHIVE="$ROOT/build/release/Zyryn-linux-x64.tar.gz"
OUTPUT="$ROOT/build/release/Zyryn-linux-x64.run"
VERIFY_WRAPPER=''
WORK_DIR=''

fail() {
  echo "ERROR: $*" >&2
  exit 1
}

log() {
  echo "[linux-run] $*"
}

usage() {
  printf '%s\n' \
    'Usage:' \
    '  scripts/package_linux_run.sh [--archive PATH] [--output PATH]' \
    '  scripts/package_linux_run.sh --verify-only WRAPPER' \
    '' \
    'Defaults:' \
    '  --archive  build/release/Zyryn-linux-x64.tar.gz' \
    '  --output   build/release/Zyryn-linux-x64.run'
}

cleanup() {
  if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
    rm -rf -- "$WORK_DIR"
  fi
}
trap cleanup EXIT INT TERM

while [ "$#" -gt 0 ]; do
  case "$1" in
    --archive)
      [ "$#" -ge 2 ] || fail '--archive requires a path.'
      ARCHIVE=$2
      shift 2
      ;;
    --output)
      [ "$#" -ge 2 ] || fail '--output requires a path.'
      OUTPUT=$2
      shift 2
      ;;
    --verify-only)
      [ "$#" -ge 2 ] || fail '--verify-only requires a wrapper path.'
      [ -z "$VERIFY_WRAPPER" ] || fail '--verify-only may be supplied only once.'
      VERIFY_WRAPPER=$2
      shift 2
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      fail "Unknown argument '$1'. Run with --help for usage."
      ;;
  esac
done

require_command() {
  command -v "$1" >/dev/null 2>&1 || fail "Required command '$1' was not found."
}

sha256_of() {
  sha256sum "$1" | awk '{print $1}'
}

read_pubspec_version() {
  awk '
    /^version:[[:space:]]*/ {
      value = $0
      sub(/^version:[[:space:]]*/, "", value)
      sub(/[[:space:]]*#.*/, "", value)
      gsub(/[[:space:]]/, "", value)
      print value
      exit
    }
  ' "$ROOT/pubspec.yaml"
}

metadata_value() {
  local name=$1
  local header=$2
  local matches value
  matches="$(grep -Ec "^${name}='[^']*'$" "$header" || true)"
  [ "$matches" = '1' ] || fail "Wrapper metadata '$name' is missing or duplicated."
  value="$(sed -n "s/^${name}='\\([^']*\\)'$/\\1/p" "$header")"
  printf '%s\n' "$value"
}

extract_wrapper_parts() {
  local wrapper=$1
  local header=$2
  local payload=$3
  local payload_line

  awk -v marker="$PAYLOAD_MARKER" \
    '$0 == marker { found = 1; exit } { print } END { if (!found) exit 1 }' \
    "$wrapper" >"$header" || fail 'Wrapper payload marker is missing.'
  payload_line="$(awk -v marker="$PAYLOAD_MARKER" \
    '$0 == marker { print NR + 1; found = 1; exit } END { if (!found) exit 1 }' \
    "$wrapper")" || fail 'Wrapper payload marker is missing.'
  tail -n "+$payload_line" "$wrapper" >"$payload"
}

verify_wrapper() {
  local wrapper=$1
  local verify_dir header payload format version build expected_version
  local expected_build declared_sha declared_bytes actual_sha actual_bytes

  [ -f "$wrapper" ] || fail "Linux wrapper not found: $wrapper"
  [ -x "$wrapper" ] || fail "Linux wrapper is not executable: $wrapper"

  verify_dir="$(mktemp -d "$WORK_DIR/verify.XXXXXX")"
  header=$verify_dir/header.sh
  payload=$verify_dir/payload.tar.gz
  extract_wrapper_parts "$wrapper" "$header" "$payload"
  sh -n "$header" || fail 'Linux wrapper shell header has invalid syntax.'

  format="$(metadata_value ZYRYN_WRAPPER_FORMAT "$header")"
  version="$(metadata_value ZYRYN_APP_VERSION "$header")"
  build="$(metadata_value ZYRYN_APP_BUILD "$header")"
  declared_sha="$(metadata_value ZYRYN_PAYLOAD_SHA256 "$header")"
  declared_bytes="$(metadata_value ZYRYN_PAYLOAD_BYTES "$header")"

  [ "$format" = "$WRAPPER_FORMAT" ] || \
    fail "Unsupported wrapper format '$format'."
  case "$version" in
    ''|*[!0-9A-Za-z._+-]*) fail "Unsafe wrapper version '$version'." ;;
  esac
  case "$build" in
    ''|*[!0-9]*) fail "Invalid wrapper build '$build'." ;;
  esac
  case "$declared_sha" in
    *[!0-9a-f]*|'') fail 'Wrapper payload SHA-256 is malformed.' ;;
  esac
  [ "${#declared_sha}" -eq 64 ] || fail 'Wrapper payload SHA-256 is malformed.'
  case "$declared_bytes" in
    ''|*[!0-9]*) fail 'Wrapper payload byte count is malformed.' ;;
  esac

  expected_version="$(read_pubspec_version)"
  expected_build=${expected_version#*+}
  expected_version=${expected_version%%+*}
  [ "$version" = "$expected_version" ] || \
    fail "Wrapper version '$version' does not match pubspec '$expected_version'."
  [ "$build" = "$expected_build" ] || \
    fail "Wrapper build '$build' does not match pubspec '$expected_build'."

  actual_sha="$(sha256_of "$payload")"
  actual_bytes="$(stat -c '%s' "$payload")"
  [ "$actual_sha" = "$declared_sha" ] || \
    fail 'Wrapper embedded payload does not match its declared SHA-256.'
  [ "$actual_bytes" = "$declared_bytes" ] || \
    fail 'Wrapper embedded payload does not match its declared byte count.'

  bash "$SCRIPT_DIR/package_linux_portable.sh" --verify-only "$payload"
  log "verified $(basename -- "$wrapper")"
  log "  version: $version+$build"
  log "  payload sha256: $actual_sha"
  log "  wrapper sha256: $(sha256_of "$wrapper")"
  log "  bytes: $(stat -c '%s' "$wrapper")"
}

package_wrapper() {
  local pubspec_version version build payload_sha payload_bytes
  local temp_wrapper rendered_header

  [ -f "$ARCHIVE" ] || fail "Portable Linux archive not found: $ARCHIVE"
  [ -f "$TEMPLATE" ] || fail "Linux wrapper template not found: $TEMPLATE"
  [ "$ARCHIVE" != "$OUTPUT" ] || fail '--archive and --output must be different paths.'

  bash "$SCRIPT_DIR/package_linux_portable.sh" --verify-only "$ARCHIVE"

  pubspec_version="$(read_pubspec_version)"
  version=${pubspec_version%%+*}
  build=${pubspec_version#*+}
  [ "$version" != "$pubspec_version" ] || \
    fail 'pubspec.yaml version must include a +build suffix.'
  case "$version" in
    ''|*[!0-9A-Za-z._+-]*) fail "Unsafe pubspec version '$version'." ;;
  esac
  case "$build" in
    ''|*[!0-9]*) fail "Invalid pubspec build '$build'." ;;
  esac

  payload_sha="$(sha256_of "$ARCHIVE")"
  payload_bytes="$(stat -c '%s' "$ARCHIVE")"
  rendered_header=$WORK_DIR/header.sh
  sed \
    -e "s/@APP_VERSION@/$version/g" \
    -e "s/@APP_BUILD@/$build/g" \
    -e "s/@PAYLOAD_SHA256@/$payload_sha/g" \
    -e "s/@PAYLOAD_BYTES@/$payload_bytes/g" \
    "$TEMPLATE" >"$rendered_header"

  [ "$(tail -n 1 "$rendered_header")" = "$PAYLOAD_MARKER" ] || \
    fail 'Linux wrapper template must end with the payload marker.'
  [ "$(grep -Fxc "$PAYLOAD_MARKER" "$rendered_header")" = '1' ] || \
    fail 'Linux wrapper template must contain exactly one payload marker.'

  mkdir -p -- "$(dirname -- "$OUTPUT")"
  temp_wrapper=$WORK_DIR/Zyryn-linux-x64.run
  {
    cat "$rendered_header"
    cat "$ARCHIVE"
  } >"$temp_wrapper"
  chmod 0755 "$temp_wrapper"

  verify_wrapper "$temp_wrapper"
  mv -f -- "$temp_wrapper" "$OUTPUT"
  log "wrote $OUTPUT"
}

for required_command in awk bash cat chmod grep mktemp mv rm sed sha256sum sh \
  stat tail; do
  require_command "$required_command"
done
[ -f "$ROOT/pubspec.yaml" ] || fail "pubspec.yaml not found below $ROOT"
[ -f "$SCRIPT_DIR/package_linux_portable.sh" ] || \
  fail 'Portable Linux packager/verifier is missing.'

WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/zyryn-linux-run.XXXXXX")"

if [ -n "$VERIFY_WRAPPER" ]; then
  verify_wrapper "$VERIFY_WRAPPER"
else
  package_wrapper
fi
