#!/usr/bin/env bash
set -euo pipefail
DIVISIONDESK_URL="${DIVISIONDESK_URL:-https://divisiondesk.com}"
PREFLIGHT_URL="${DIVISIONDESK_PREFLIGHT_URL:-$DIVISIONDESK_URL/api/license-preflight.php}"
TARGET="${DIVISIONDESK_TARGET:-$(pwd)}"
SELF="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
ORIGINAL_SELF="${DIVISIONDESK_ORIGINAL_SELF:-$SELF}"
fail(){ echo "ERROR: $*" >&2; exit 1; }

command -v php >/dev/null 2>&1 || fail "PHP CLI is required."
PHP_VERSION="$(php -r 'echo PHP_VERSION;')"; echo "DivisionDesk Installer"; echo "======================"; echo; echo "✓ PHP $PHP_VERSION detected"
[ ! -f "$TARGET/storage/setup/installed.lock" ] || fail "DivisionDesk is already installed in $TARGET. Use the Core updater/repair tools."
mkdir -p "$TARGET" || fail "Installation target cannot be created: $TARGET"
[ -w "$TARGET" ] || fail "Installation target is not writable: $TARGET"

# Workspace fallback: system temp -> user temp -> target-local. Always invoke the
# runner through bash so noexec temporary mounts are supported.
make_work(){ local d=""; d="$(mktemp -d 2>/dev/null || true)"; if [ -n "$d" ] && [ -w "$d" ]; then echo "$d"; return; fi; d="${HOME:-$TARGET}/.divisiondesk-installer-$$"; mkdir -p "$d" 2>/dev/null || true; if [ -w "$d" ]; then echo "$d"; return; fi; d="$TARGET/.divisiondesk-installer-$$"; mkdir -p "$d" || return 1; echo "$d"; }
if [ "${DIVISIONDESK_INSTALLER_REEXEC:-0}" != "1" ]; then
  BOOTSTRAP_TMP="$(make_work)" || fail "No writable installer workspace is available."
  RUNNER="$BOOTSTRAP_TMP/DivisionDesk-install.runner"; cp "$SELF" "$RUNNER" || fail "Could not create immutable installer runner."
  export DIVISIONDESK_INSTALLER_REEXEC=1 DIVISIONDESK_ORIGINAL_SELF="$ORIGINAL_SELF" DIVISIONDESK_TARGET="$TARGET" DIVISIONDESK_BOOTSTRAP_TMP="$BOOTSTRAP_TMP"
  exec bash "$RUNNER"
fi
WORK="${DIVISIONDESK_BOOTSTRAP_TMP:-$(make_work)}"; mkdir -p "$WORK"; cleanup(){ rm -rf "$WORK" 2>/dev/null || true; }; trap cleanup EXIT

LICENSE_KEY="${DIVISIONDESK_LICENSE_KEY:-}"; INSTALL_DOMAIN="${DIVISIONDESK_INSTALL_DOMAIN:-}"; INSTALL_CLASS="${DIVISIONDESK_INSTALL_CLASS:-production}"
if [ -z "$LICENSE_KEY" ]; then [ -t 0 ] || fail "Set DIVISIONDESK_LICENSE_KEY for non-interactive installation."; read -r -s -p "DivisionDesk license key: " LICENSE_KEY; echo; fi
if [ -z "$INSTALL_DOMAIN" ]; then [ -t 0 ] || fail "Set DIVISIONDESK_INSTALL_DOMAIN for non-interactive installation."; read -r -p "Installation domain (example.org): " INSTALL_DOMAIN; fi
case "$INSTALL_CLASS" in production|staging|development|demo|review) ;; *) fail "Invalid installation class.";; esac
INSTALL_DOMAIN="${INSTALL_DOMAIN#http://}"; INSTALL_DOMAIN="${INSTALL_DOMAIN#https://}"; INSTALL_DOMAIN="${INSTALL_DOMAIN%%/*}"
[ -n "$LICENSE_KEY" ] && [ -n "$INSTALL_DOMAIN" ] || fail "License key and installation domain are required."

# public_html is preferred automatically when present. Override explicitly for
# managed /public or subfolder deployments.
PUBLIC_PATH="${DIVISIONDESK_PUBLIC_PATH:-}"
if [ -z "$PUBLIC_PATH" ]; then if [ -d "$TARGET/public_html" ]; then PUBLIC_PATH="$TARGET/public_html"; else PUBLIC_PATH="$TARGET/public"; fi; fi
mkdir -p "$PUBLIC_PATH" || fail "Public filesystem path cannot be created: $PUBLIC_PATH"
[ -w "$PUBLIC_PATH" ] || fail "Public filesystem path is not writable: $PUBLIC_PATH"
echo "✓ Application root: $TARGET"; echo "✓ Public web root: $PUBLIC_PATH"

# Conservative disk-space preflight when df is available.
if command -v df >/dev/null 2>&1; then AVAIL_KB="$(df -Pk "$TARGET" 2>/dev/null | awk 'NR==2{print $4}' || true)"; if [ -n "$AVAIL_KB" ] && [ "$AVAIL_KB" -lt 102400 ]; then fail "At least 100 MB of free disk space is required before installation."; fi; fi

cat > "$WORK/http.php" <<'PHP'
<?php
[$self,$url,$out,$method,$body]=array_pad($argv,5,'');$headers=['Accept: application/json'];if($method==='POST')$headers[]='Content-Type: application/json';
if(function_exists('curl_init')){$c=curl_init($url);curl_setopt_array($c,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_FOLLOWLOCATION=>true,CURLOPT_CONNECTTIMEOUT=>15,CURLOPT_TIMEOUT=>90,CURLOPT_HTTPHEADER=>$headers]);if($method==='POST'){curl_setopt($c,CURLOPT_POST,true);curl_setopt($c,CURLOPT_POSTFIELDS,$body);} $data=curl_exec($c);$code=(int)curl_getinfo($c,CURLINFO_RESPONSE_CODE);$err=curl_error($c);curl_close($c);if($data===false){fwrite(STDERR,"HTTP error: $err\n");exit(2);}}
elseif((bool)ini_get('allow_url_fopen')){$opts=['http'=>['method'=>$method?:'GET','timeout'=>90,'ignore_errors'=>true,'header'=>implode("\r\n",$headers)]];if($method==='POST')$opts['http']['content']=$body;$data=@file_get_contents($url,false,stream_context_create($opts));$code=0;foreach($http_response_header??[] as $h)if(preg_match('#^HTTP/\\S+\\s+(\\d+)#',$h,$m))$code=(int)$m[1];if($data===false){fwrite(STDERR,"HTTPS download failed. Enable PHP cURL or allow_url_fopen.\n");exit(2);}}
else{fwrite(STDERR,"No HTTPS transport is available. Enable PHP cURL or allow_url_fopen.\n");exit(2);}if(file_put_contents($out,$data)===false)exit(3);echo $code;
PHP
http(){ php "$WORK/http.php" "$1" "$2" "$3" "${4:-GET}" "${5:-}"; }

REQ="$(php -r 'echo json_encode(["license_key"=>$argv[1],"domain"=>$argv[2],"installation_class"=>$argv[3]],JSON_UNESCAPED_SLASHES);' "$LICENSE_KEY" "$INSTALL_DOMAIN" "$INSTALL_CLASS")"
echo "Validating license and authorized domain..."; HTTP="$(http "$PREFLIGHT_URL" "$WORK/auth.json" POST "$REQ")"; [ "$HTTP" -ge 200 ] && [ "$HTTP" -lt 300 ] || { php -r '$d=json_decode(@file_get_contents($argv[1]),true);fwrite(STDERR,"ERROR: ".($d["error"]??"License validation failed.")."\n");' "$WORK/auth.json"; exit 1; }
php -r '$d=json_decode(file_get_contents($argv[1]),true);$r=$d["release"]??[];if(empty($d["ok"])||empty($d["valid"])||empty($r["version"])||empty($r["download_url"])||!preg_match("/^[a-f0-9]{64}$/i",$r["sha256"]??"")||(int)($r["package_size"]??0)<1)exit(2);if(version_compare(PHP_VERSION,$r["minimum_php"]??"8.1","<"))exit(3);file_put_contents($argv[2],implode("\n",[$r["version"],$r["download_url"],$r["sha256"],$r["package_size"]]));' "$WORK/auth.json" "$WORK/meta" || fail "Install authorization is incomplete or incompatible with this PHP version."
mapfile -t META < "$WORK/meta"; VERSION="${META[0]}"; PACKAGE_URL="${META[1]}"; EXPECTED_SHA="${META[2]}"; EXPECTED_SIZE="${META[3]}"; echo "✓ License validated"; echo "✓ Authorized Core: $VERSION"
echo "Downloading protected Core package..."; HTTP="$(http "$PACKAGE_URL" "$WORK/core.zip" GET)"; [ "$HTTP" -ge 200 ] && [ "$HTTP" -lt 300 ] || fail "Protected Core download returned HTTP $HTTP."
php -r '$f=$argv[1];if(!is_file($f)||filesize($f)!==(int)$argv[2]){fwrite(STDERR,"Package size verification failed.\n");exit(2);}if(!hash_equals(strtolower($argv[3]),strtolower(hash_file("sha256",$f)))){fwrite(STDERR,"SHA-256 verification failed.\n");exit(3);}' "$WORK/core.zip" "$EXPECTED_SIZE" "$EXPECTED_SHA" || fail "Downloaded Core package failed verification."
echo "✓ Size and SHA-256 verified"

STAGE="$WORK/stage"; mkdir -p "$STAGE"
if php -r 'exit(class_exists("ZipArchive")?0:1);'; then
 php -r '$z=new ZipArchive;if($z->open($argv[1])!==true)exit(2);for($i=0;$i<$z->numFiles;$i++){$n=$z->getNameIndex($i);if($n===""||str_contains($n,"../")||str_contains($n,"..\\")||str_starts_with($n,"/")||preg_match("/^[A-Za-z]:[\\\\\\/]/",$n))exit(3);$ops=0;$attr=0;if($z->getExternalAttributesIndex($i,$ops,$attr)&&$ops===ZipArchive::OPSYS_UNIX&&((($attr>>16)&0170000)===0120000))exit(4);}if(!$z->extractTo($argv[2]))exit(5);$z->close();' "$WORK/core.zip" "$STAGE" || fail "Core ZIP validation/extraction failed."
elif command -v unzip >/dev/null 2>&1; then
 unzip -Z1 "$WORK/core.zip" | grep -Eq '(^/|(^|/)\.\.(/|$)|^[A-Za-z]:)' && fail "Unsafe path found in Core ZIP."; unzip -q "$WORK/core.zip" -d "$STAGE" || fail "Could not extract Core ZIP."
else fail "ZIP extraction requires PHP ZipArchive or the unzip command."; fi
[ -f "$STAGE/public/index.php" ] && [ -f "$STAGE/app/Core/Database.php" ] && [ -f "$STAGE/config/version.php" ] || fail "Core package is incomplete. No installed files were changed."

# Fresh-install transaction: target must not contain a prior DivisionDesk app.
for d in app config database themes addons; do [ ! -e "$TARGET/$d" ] || fail "Target already contains $d. Refusing a partial/destructive fresh install."; done
for d in app config database themes addons bin widgets widget-packs; do [ ! -e "$STAGE/$d" ] || cp -a "$STAGE/$d" "$TARGET/"; done
for f in manifest.json README.md CHANGELOG.md MODULE-SDK-3.5.md DivisionDesk-install; do [ ! -f "$STAGE/$f" ] || cp -a "$STAGE/$f" "$TARGET/$f"; done
cp -a "$STAGE/public/." "$PUBLIC_PATH/" || fail "Could not deploy public files to $PUBLIC_PATH."
# External public roots can still bootstrap the application via this protected PHP marker.
if [ "$(cd "$PUBLIC_PATH/.." 2>/dev/null && pwd || true)" != "$(cd "$TARGET" && pwd)" ]; then php -r 'file_put_contents($argv[2],"<?php return ".var_export($argv[1],true).";\n");' "$TARGET" "$PUBLIC_PATH/.divisiondesk-root.php"; fi
mkdir -p "$TARGET/storage/setup" "$TARGET/storage/logs" "$TARGET/storage/updates" "$TARGET/storage/data"
php -r '$dir=$argv[1]."/storage/setup";$data=["license_key"=>$argv[2],"domain"=>$argv[3],"installation_class"=>$argv[4],"validated_at"=>gmdate("c")];file_put_contents($dir."/license-bootstrap.json",json_encode($data,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES),LOCK_EX);@chmod($dir."/license-bootstrap.json",0600);$t=bin2hex(random_bytes(24));file_put_contents($dir."/token",$t,LOCK_EX);@chmod($dir."/token",0600);file_put_contents($dir."/deployment-bootstrap.json",json_encode(["public_path"=>$argv[5]],JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES),LOCK_EX);file_put_contents($dir."/installer-cleanup.json",json_encode(["path"=>$argv[6],"created_at"=>date(DATE_ATOM)],JSON_UNESCAPED_SLASHES),LOCK_EX);echo $t;' "$TARGET" "$LICENSE_KEY" "$INSTALL_DOMAIN" "$INSTALL_CLASS" "$PUBLIC_PATH" "$ORIGINAL_SELF" > "$WORK/token"
TOKEN="$(cat "$WORK/token")"; chmod -R u+rwX,g+rwX "$TARGET/storage" 2>/dev/null || true
echo "✓ Core $VERSION staged and deployed successfully"; echo; echo "Browser setup is ready."; echo "Public web root: $PUBLIC_PATH"; if [ -n "${DIVISIONDESK_SITE_URL:-}" ]; then echo "Open: ${DIVISIONDESK_SITE_URL%/}/setup.php?token=$TOKEN"; else echo "Open your domain's /setup.php?token=$TOKEN URL."; fi
