#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
ENV_FILE=".env"
STORAGE_MODE="${KORO_STORAGE_MODE:-}"
DB_HOST="${KORO_DB_HOST:-}"
DB_PORT="${KORO_DB_PORT:-5432}"
DB_USER="${KORO_DB_USER:-}"
DB_PASSWORD="${KORO_DB_PASSWORD:-}"
GATEWAY_SERVICE_URL="${KORO_GATEWAY_SERVICE_URL-https://koroerp-api.koroworks.com}"
AI_PAIRING_CODE="${KORO_AI_PAIRING_CODE:-}"
AI_INGEST_TOKEN="${KORO_AI_INGEST_TOKEN:-}"
AI_ERP_AUTH_BASE_URL="${KORO_AI_ERP_AUTH_BASE_URL:-}"
AUTH_SERVICE_URL="${KORO_AUTH_SERVICE_URL:-}"
AI_WALLET_MODE="${KORO_AI_WALLET_MODE:-remote}"
BYOK_ENCRYPTION_KEY="${KORO_BYOK_ENCRYPTION_KEY:-}"
AI_PORT="${KORO_AI_PORT:-7025}"
REGISTRY="${KORO_REGISTRY:-korobosta}"
VERSION="${KORO_VERSION:-latest}"
MAX_TRAINING_WORKERS="${KORO_MAX_TRAINING_WORKERS:-2}"
STORAGE_ENDPOINT="${KORO_STORAGE_ENDPOINT:-}"
STORAGE_ACCESS_KEY="${KORO_STORAGE_ACCESS_KEY:-}"
STORAGE_SECRET_KEY="${KORO_STORAGE_SECRET_KEY:-}"
STORAGE_BUCKET="${KORO_STORAGE_BUCKET:-koro-erp}"
STORAGE_REGION="${KORO_STORAGE_REGION:-us-east-1}"
STORAGE_USE_SSL="${KORO_STORAGE_USE_SSL:-true}"
MINIO_ROOT_USER="${KORO_MINIO_ROOT_USER:-minioadmin}"
MINIO_ROOT_PASSWORD="${KORO_MINIO_ROOT_PASSWORD:-}"
MINIO_IMAGE="${KORO_MINIO_IMAGE:-}"
JWT_PUBLIC_KEY_SOURCE="${KORO_JWT_PUBLIC_KEY_PATH:-}"
JWT_PUBLIC_KEY_PEM="${KORO_JWT_PUBLIC_KEY_PEM:-}"

fail() { echo "[FAIL] $*" >&2; exit 1; }
need() { [[ -n "$1" ]] || fail "$2"; }

urlencode() {
  local LC_ALL=C value="$1" character index
  for ((index = 0; index < ${#value}; index++)); do
    character="${value:index:1}"
    case "$character" in
      [a-zA-Z0-9.~_-]) printf '%s' "$character" ;;
      *) printf '%%%02X' "'$character" ;;
    esac
  done
}

dotenv_value() {
  local value="$1"
  [[ "$value" != *$'\n'* && "$value" != *$'\r'* ]] || fail "Configuration values must not contain newlines"
  value="${value//\'/\\\'}"
  printf "'%s'" "$value"
}

command -v docker >/dev/null 2>&1 || fail "Docker is required"
docker compose version >/dev/null 2>&1 || fail "Docker Compose v2 is required"
command -v openssl >/dev/null 2>&1 || fail "OpenSSL is required to validate the ERP JWT public key"
[[ -f docker-compose.yml ]] || fail "docker-compose.yml is missing"

if [[ ! -e "$ENV_FILE" && -f .env.ai ]]; then
  (umask 077; set -o noclobber; cat .env.ai > "$ENV_FILE") || fail "Could not migrate legacy .env.ai"
  echo "[OK] Copied legacy .env.ai to .env; the original backup is unchanged."
fi

saved_byok_key=""
saved_setting_names="|"
if [[ -f "$ENV_FILE" ]]; then
  saved_environment="$(docker compose --env-file "$ENV_FILE" -f - config --environment <<'COMPOSE'
services:
  settings:
    image: scratch
COMPOSE
  )" || fail "Existing .env cannot be parsed; it has not been changed"
  while IFS= read -r entry; do
    name="${entry%%=*}"
    value="${entry#*=}"
    case "$name" in
      BYOK_ENCRYPTION_KEY) saved_byok_key="$value" ;;
      DB_HOST|DB_PORT|DB_USER|DB_PASSWORD|GATEWAY_SERVICE_URL|AI_PAIRING_CODE|AI_INGEST_TOKEN|AI_ERP_AUTH_BASE_URL|AUTH_SERVICE_URL|AI_WALLET_MODE|STORAGE_MODE|STORAGE_ENDPOINT|STORAGE_ACCESS_KEY|STORAGE_SECRET_KEY|STORAGE_BUCKET|STORAGE_REGION|STORAGE_USE_SSL|MINIO_ROOT_USER|MINIO_ROOT_PASSWORD|MINIO_IMAGE|AI_PORT|REGISTRY|VERSION|MAX_TRAINING_WORKERS)
        saved_setting_names+="$name|"
        override="KORO_$name"
        if [[ ! -v "$override" ]]; then printf -v "$name" '%s' "$value"; fi
        ;;
    esac
  done <<< "$saved_environment"
fi
if [[ -f ../apis/.env ]]; then
  erp_environment="$(docker compose --env-file ../apis/.env -f - config --environment <<'COMPOSE'
services:
  settings:
    image: scratch
COMPOSE
  )" || fail "Existing ERP environment cannot be parsed; it has not been changed"
  erp_host=""
  while IFS= read -r entry; do
    [[ "${entry%%=*}" == DB_HOST ]] && erp_host="${entry#*=}"
  done <<< "$erp_environment"
  while IFS= read -r entry; do
    name="${entry%%=*}"; value="${entry#*=}"
    [[ -n "$value" ]] || continue
    [[ "$name" == API_BASE_URL ]] && name=GATEWAY_SERVICE_URL
    case "$name" in
      GATEWAY_SERVICE_URL|AI_INGEST_TOKEN|DB_HOST|DB_PORT|DB_USER|DB_PASSWORD)
        override="KORO_$name"
        [[ ! -v "$override" && "$saved_setting_names" != *"|$name|"* ]] || continue
        case "$name" in
          DB_HOST|DB_PORT|DB_USER|DB_PASSWORD)
            [[ -n "$erp_host" && "$erp_host" != postgres && "$erp_host" != localhost && "$erp_host" != 127.0.0.1 && "$erp_host" != ::1 ]] || continue
            ;;
        esac
        printf -v "$name" '%s' "$value"
        ;;
    esac
  done <<< "$erp_environment"
  echo "[OK] Reused available sibling ERP connection settings without exposing credentials."
fi
if [[ -n "$saved_byok_key" ]]; then
  [[ -z "$BYOK_ENCRYPTION_KEY" || "$BYOK_ENCRYPTION_KEY" == "$saved_byok_key" ]] || fail "Refusing to replace the existing BYOK encryption key; key rotation requires re-encrypting saved credentials"
  BYOK_ENCRYPTION_KEY="$saved_byok_key"
fi
if [[ -z "$BYOK_ENCRYPTION_KEY" ]]; then
  BYOK_ENCRYPTION_KEY="$(openssl rand -base64 32)"
fi
if [[ $(printf '%s' "$BYOK_ENCRYPTION_KEY" | wc -c) -ne 32 ]]; then
  normalized_key="$(printf '%s' "$BYOK_ENCRYPTION_KEY" | tr '_-' '/+')"
  while (( ${#normalized_key} % 4 != 0 )); do normalized_key+='='; done
  key_bytes="$(printf '%s' "$normalized_key" | openssl base64 -d -A | wc -c)" || fail "BYOK encryption key must contain 32 bytes or base64-encoded 32 bytes"
  [[ "$key_bytes" -eq 32 ]] || fail "BYOK encryption key must contain 32 bytes or base64-encoded 32 bytes"
fi

case "$AI_WALLET_MODE" in
  remote|hybrid)
    echo "Platform access uses ERP Auth pairing; direct installation credentials are no longer supported."
    if [[ -z "$AI_PAIRING_CODE" ]]; then
      echo "In the ERP Business app, open AI > Settings > Connect AI Server."
      echo "As a tenant administrator, select Generate Pairing Code and copy it here."
      echo "The code expires after 5 minutes and can be used once. Generate it when this installer is ready."
      read -rsp "One-time ERP AI pairing code: " AI_PAIRING_CODE || true; echo
    fi
    need "$AI_PAIRING_CODE" "ERP AI pairing code is required; direct installation credentials are no longer supported"
    ;;
  local) ;;
  *) fail "AI_WALLET_MODE must be remote, hybrid, or local" ;;
esac

echo "AI requires the JWT public key from your existing Go ERP installation."
echo "Obtain keys/public.pem from that ERP installation's administrator."
echo "For a separate AI server, transfer only public.pem and place it in keys/public.pem"
echo "or set KORO_JWT_PUBLIC_KEY_PATH to its local path. Do not generate a new key pair"
echo "or copy private.pem or licence_public.pem."

[[ -z "$JWT_PUBLIC_KEY_SOURCE" || -z "$JWT_PUBLIC_KEY_PEM" ]] || fail "Supply either a public-key path or pasted PEM, not both"
if [[ -z "$JWT_PUBLIC_KEY_SOURCE" && -z "$JWT_PUBLIC_KEY_PEM" && ! -s keys/public.pem && -z "$AI_PAIRING_CODE" ]]; then
  if [[ -s ../apis/keys/public.pem ]]; then
    JWT_PUBLIC_KEY_SOURCE="../apis/keys/public.pem"
  else
    echo "Enter the local public.pem path OR paste the complete ERP public PEM, including its BEGIN and END lines."
    read -r key_input || true
    key_input="${key_input%$'\r'}"
    if [[ "$key_input" == '-----BEGIN PUBLIC KEY-----' || "$key_input" == '-----BEGIN RSA PUBLIC KEY-----' ]]; then
      JWT_PUBLIC_KEY_PEM="$key_input"
      while IFS= read -r key_line; do
        key_line="${key_line%$'\r'}"
        JWT_PUBLIC_KEY_PEM+=$'\n'"$key_line"
        [[ ${#JWT_PUBLIC_KEY_PEM} -le 16384 ]] || fail "Public PEM exceeds the input size limit"
        [[ "$key_line" == '-----END PUBLIC KEY-----' || "$key_line" == '-----END RSA PUBLIC KEY-----' ]] && break
      done
    else
      [[ "$key_input" != *'PRIVATE KEY'* ]] || fail "Use the ERP public key, never its private key"
      JWT_PUBLIC_KEY_SOURCE="$key_input"
      need "$JWT_PUBLIC_KEY_SOURCE" "Provide the ERP public-key path or paste its complete public PEM"
    fi
  fi
fi
if [[ -n "$JWT_PUBLIC_KEY_PEM" ]]; then
  [[ ${#JWT_PUBLIC_KEY_PEM} -le 16384 ]] || fail "Public PEM exceeds the input size limit"
  [[ "$JWT_PUBLIC_KEY_PEM" != *'PRIVATE KEY'* ]] || fail "Use the ERP public key, never its private key"
  printf '%s\n' "$JWT_PUBLIC_KEY_PEM" | openssl rsa -pubin -noout >/dev/null 2>&1 || fail "Pasted PEM must be a complete, valid RSA public key"
  mkdir -p keys
  public_key_temp="$(mktemp keys/.public.pem.XXXXXX)"
  printf '%s\n' "$JWT_PUBLIC_KEY_PEM" > "$public_key_temp"
  chmod 644 "$public_key_temp"
  mv "$public_key_temp" keys/public.pem
fi
if [[ -n "$JWT_PUBLIC_KEY_SOURCE" ]]; then
  [[ -s "$JWT_PUBLIC_KEY_SOURCE" ]] || fail "ERP JWT public key file does not exist or is empty"
  grep -q -- 'PRIVATE KEY' "$JWT_PUBLIC_KEY_SOURCE" && fail "Use the ERP public key, never its private key"
  openssl rsa -pubin -in "$JWT_PUBLIC_KEY_SOURCE" -noout >/dev/null 2>&1 || fail "ERP JWT public key must be a valid RSA public PEM"
  mkdir -p keys
  if [[ ! "$JWT_PUBLIC_KEY_SOURCE" -ef keys/public.pem ]]; then
    cp "$JWT_PUBLIC_KEY_SOURCE" keys/public.pem
  fi
fi
mkdir -p keys
if [[ -s keys/public.pem ]]; then
  grep -q -- 'PRIVATE KEY' keys/public.pem && fail "keys/public.pem must not contain a private key"
  openssl rsa -pubin -in keys/public.pem -noout >/dev/null 2>&1 || fail "keys/public.pem must be a valid ERP RSA public key"
  chmod 644 keys/public.pem
else
  [[ -n "$AI_PAIRING_CODE" ]] || fail "ERP JWT public key is missing from keys/public.pem"
  echo "ERP pairing will supply the JWT public key at AI startup."
fi

if [[ -z "$STORAGE_MODE" ]]; then
  echo "AI object storage:"
  echo "  1) Local disk"
  echo "  2) Bundled MinIO"
  echo "  3) External S3-compatible storage"
  read -rp "Choose [1/2/3] (default: 1): " choice
  case "${choice:-1}" in
    1) STORAGE_MODE=local ;;
    2) STORAGE_MODE=minio ;;
    3) STORAGE_MODE=s3 ;;
    *) fail "Choose 1, 2, or 3" ;;
  esac
fi

if [[ -z "$DB_HOST" ]]; then read -rp "PostgreSQL host: " DB_HOST; fi
if [[ -z "$DB_USER" ]]; then read -rp "PostgreSQL username: " DB_USER; fi
if [[ -z "$DB_PASSWORD" ]]; then read -rsp "PostgreSQL password: " DB_PASSWORD; echo; fi
if [[ -z "$GATEWAY_SERVICE_URL" ]]; then read -rp "ERP gateway/reverse-proxy URL (e.g. https://koroerp-api.example.com): " GATEWAY_SERVICE_URL; fi
need "$DB_HOST" "PostgreSQL host is required"
need "$DB_USER" "PostgreSQL username is required"
need "$DB_PASSWORD" "PostgreSQL password is required"
need "$GATEWAY_SERVICE_URL" "ERP gateway URL is required"

if [[ -z "$AUTH_SERVICE_URL" ]]; then
  read -rp "Auth service URL reachable from AI (optional; leave blank to disable live user counts): " AUTH_SERVICE_URL || true
fi

case "$STORAGE_MODE" in
  local)
    STORAGE_ENDPOINT=""
    STORAGE_ACCESS_KEY=""
    STORAGE_SECRET_KEY=""
    ;;
  minio)
    if [[ -z "$MINIO_IMAGE" ]]; then
      read -rp "MinIO image you built or approved (minio/minio:latest is unavailable): " MINIO_IMAGE || true
    fi
    need "$MINIO_IMAGE" "MinIO requires KORO_MINIO_IMAGE; use local disk or external S3 otherwise"
    if [[ -z "$MINIO_ROOT_PASSWORD" ]]; then read -rsp "MinIO root password: " MINIO_ROOT_PASSWORD; echo; fi
    need "$MINIO_ROOT_PASSWORD" "MinIO root password is required"
    STORAGE_ENDPOINT="http://minio:9000"
    STORAGE_ACCESS_KEY="$MINIO_ROOT_USER"
    STORAGE_SECRET_KEY="$MINIO_ROOT_PASSWORD"
    STORAGE_USE_SSL=false
    ;;
  s3)
    if [[ -z "$STORAGE_ACCESS_KEY" ]]; then
      read -rp "S3 endpoint (leave blank for AWS S3; no bucket path): " STORAGE_ENDPOINT
      read -rp "S3 access key: " STORAGE_ACCESS_KEY
      read -rp "S3 bucket [$STORAGE_BUCKET]: " value; STORAGE_BUCKET="${value:-$STORAGE_BUCKET}"
      read -rp "S3 region [$STORAGE_REGION]: " value; STORAGE_REGION="${value:-$STORAGE_REGION}"
    fi
    if [[ -z "$STORAGE_SECRET_KEY" ]]; then read -rsp "S3 secret key: " STORAGE_SECRET_KEY; echo; fi
    need "$STORAGE_ACCESS_KEY" "S3 access key is required"
    need "$STORAGE_SECRET_KEY" "S3 secret key is required"
    ;;
  *) fail "Unsupported storage mode: $STORAGE_MODE" ;;
esac

umask 077
env_temp="$(mktemp "$ENV_FILE.XXXXXX")"
trap 'rm -f "$env_temp"' EXIT
cat > "$env_temp" <<EOF
DB_HOST=$DB_HOST
DB_PORT=$DB_PORT
DB_USER=$(dotenv_value "$DB_USER")
DB_PASSWORD=$(dotenv_value "$DB_PASSWORD")
DB_USER_URLENCODED=$(urlencode "$DB_USER")
DB_PASSWORD_URLENCODED=$(urlencode "$DB_PASSWORD")
GATEWAY_SERVICE_URL=$GATEWAY_SERVICE_URL
AI_PAIRING_CODE=$(dotenv_value "$AI_PAIRING_CODE")
AI_INGEST_TOKEN=$(dotenv_value "$AI_INGEST_TOKEN")
AI_ERP_AUTH_BASE_URL=$(dotenv_value "$AI_ERP_AUTH_BASE_URL")
AUTH_SERVICE_URL=$(dotenv_value "$AUTH_SERVICE_URL")
AI_WALLET_MODE=$AI_WALLET_MODE
BYOK_ENCRYPTION_KEY=$(dotenv_value "$BYOK_ENCRYPTION_KEY")
STORAGE_MODE=$STORAGE_MODE
STORAGE_ENDPOINT=$STORAGE_ENDPOINT
STORAGE_ACCESS_KEY=$(dotenv_value "$STORAGE_ACCESS_KEY")
STORAGE_SECRET_KEY=$(dotenv_value "$STORAGE_SECRET_KEY")
STORAGE_BUCKET=$STORAGE_BUCKET
STORAGE_REGION=$STORAGE_REGION
STORAGE_USE_SSL=$STORAGE_USE_SSL
MINIO_ROOT_USER=$MINIO_ROOT_USER
MINIO_ROOT_PASSWORD=$(dotenv_value "$MINIO_ROOT_PASSWORD")
MINIO_IMAGE=$MINIO_IMAGE
AI_PORT=$AI_PORT
REGISTRY=$REGISTRY
VERSION=$VERSION
MAX_TRAINING_WORKERS=$MAX_TRAINING_WORKERS
EOF
if [[ -f "$ENV_FILE" ]]; then
  obsolete_platform_settings='^[[:space:]]*(export[[:space:]]+)?(PLATFORM_(API_URL|MTLS_API_URL|INSTALL_ID|INSTALL_KEY|JWT_SECRET|AUTH_MODE|RELAY_(TOKEN|PAYMENTS_TOKEN|NOTIFICATIONS_TOKEN|MAPS_TOKEN))|PORTAL_INTERNAL_TOKEN|INSTALL_(STATE_PATH|MTLS_REQUIRED|CLIENT_CERT|CLIENT_KEY|CA_BUNDLE)|ENROLLMENT_TOKEN|KORO_PLATFORM_(API_URL|INSTALL_ID|INSTALL_KEY|AUTH_MODE))[[:space:]]*='
  awk -v obsolete="$obsolete_platform_settings" '
    $0 !~ obsolete && $0 !~ /^(DB_HOST|DB_PORT|DB_USER|DB_PASSWORD|DB_USER_URLENCODED|DB_PASSWORD_URLENCODED|GATEWAY_SERVICE_URL|AI_PAIRING_CODE|AI_INGEST_TOKEN|AI_ERP_AUTH_BASE_URL|AUTH_SERVICE_URL|AI_WALLET_MODE|BYOK_ENCRYPTION_KEY|STORAGE_MODE|STORAGE_ENDPOINT|STORAGE_ACCESS_KEY|STORAGE_SECRET_KEY|STORAGE_BUCKET|STORAGE_REGION|STORAGE_USE_SSL|MINIO_ROOT_USER|MINIO_ROOT_PASSWORD|MINIO_IMAGE|AI_PORT|REGISTRY|VERSION|MAX_TRAINING_WORKERS)=/
  ' "$ENV_FILE" >> "$env_temp" || fail "Could not preserve existing AI environment settings"
fi
mv "$env_temp" "$ENV_FILE"
chmod 600 "$ENV_FILE"

compose_args=(--env-file "$ENV_FILE" -f docker-compose.yml)
profile_args=()
[[ "$STORAGE_MODE" == minio ]] && profile_args+=(--profile minio)
grep -q '^AI_DOMAIN=' "$ENV_FILE" && profile_args+=(--profile proxy)
docker compose "${compose_args[@]}" "${profile_args[@]}" config >/dev/null
echo "[OK] AI configuration is valid ($STORAGE_MODE storage)"
docker compose "${compose_args[@]}" "${profile_args[@]}" up -d
echo "[OK] AI containers started on port $AI_PORT; check logs and /health for application readiness"
echo "     Storage mode: $STORAGE_MODE"
echo "     ERP gateway: $GATEWAY_SERVICE_URL"
echo "     Back up .env securely: it contains the persistent BYOK encryption key."
echo "     If this AI service is on another server, run: manage.sh link-ai http://AI-SERVER:7025"
