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

log() {
  echo
  echo "==> $*"
}

warn() {
  echo "warning: $*" >&2
}

die() {
  echo "error: $*" >&2
  exit 1
}

usage() {
  cat <<'USAGE'
Usage:
  sudo scripts/03-provision.sh

The script has embedded defaults for the Bettergrow platform and can run with no
arguments on a device. Flags and environment variables still override defaults.

Environment variables may also be used:
  API=http://139.144.99.218
  SERVER_URL=http://139.144.99.218:4012
  BIN_URL=http://139.144.99.218:4012/bettergrow-embed
  BIN_PATH=/usr/local/bin/bettergrow-embed
  SERVICE_URL=http://139.144.99.218:4012/bettergrow-embed.service
  CONFIG_URL=http://139.144.99.218:4012/default-config.toml
  CLOUD_HOST=139.144.99.218
  CLOUD_PORT=8443
  AUTH_TENANT=bettergrow
  ADMIN_EMAIL=admin@bettergrow.com
  ADMIN_PASSWORD=password123
  TENANT_SLUG=bettergrow
  TENANT_ID=<uuid>
  DEVICE_NAME=<name>
  DEVICE_TYPE_SLUG=gateway
  DEVICE_TYPE_NAME="Gateway"
  DEVICE_TYPE_ID=<uuid>
  DEVICE_ID=<uuid>
  PTOKEN=<provisioning-token>
  CONFIG_PATH=/home/delphi/.config/bettergrow-embed/config.toml
  CERT_DIR=/etc/delphi

The token can be omitted on reruns once device.crt, ca.crt, and provision.json exist.

Options:
  --api <url>              Web-api base URL
  --server-url <url>       Static file server URL
  --bin-url <url>          BetterGrow embed binary URL
  --bin-path <path>        BetterGrow embed binary install path
  --service-url <url>      systemd service file URL
  --config-url <url>       Default config TOML URL
  --cloud-host <host>      Hostname/IP used by bettergrow-embed mTLS telemetry
  --cloud-port <port>      mTLS telemetry port
  --auth-tenant <tenant>   Login tenant slug, or "platform"
  --admin-email <email>    Admin login email
  --admin-password <pass>  Admin login password
  --tenant-slug <slug>     Tenant slug for platform login tenant context
  --tenant-id <uuid>       Tenant UUID for platform login tenant context
  --device-name <name>     Name for a newly-created device
  --device-type-slug <s>   Device type slug to resolve/create
  --device-type-name <n>   Device type name when creating the default type
  --device-type-id <uuid>  Optional device type UUID for a newly-created device
  --device-id <uuid>       Existing device UUID
  --token <token>          Existing one-time provisioning token
  --config <path>          Config TOML path
  --cert-dir <path>        Certificate/key directory
  -h, --help               Show this help
USAGE
}

have_command() {
  command -v "$1" >/dev/null 2>&1
}

require_command() {
  have_command "$1" || die "missing required command: $1"
}

require_root() {
  if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
    die "setup must be run as root; try: sudo scripts/setup.sh ..."
  fi
}

toml_quote() {
  local value="$1"
  value="${value//\\/\\\\}"
  value="${value//\"/\\\"}"
  printf '"%s"' "$value"
}

set_toml_value() {
  local file="$1"
  local section="$2"
  local key="$3"
  local value="$4"
  local tmp

  tmp="$(mktemp)"
  awk -v section="$section" -v key="$key" -v value="$value" '
    BEGIN {
      in_section = 0
      seen_section = 0
      set_key = 0
    }

    /^\[[^]]+\][[:space:]]*$/ {
      if (in_section && !set_key) {
        print key " = " value
        set_key = 1
      }
      in_section = ($0 == "[" section "]")
      if (in_section) {
        seen_section = 1
      }
      print
      next
    }

    {
      if (in_section && $0 ~ "^[[:space:]]*" key "[[:space:]]*=") {
        print key " = " value
        set_key = 1
        next
      }
      print
    }

    END {
      if (!seen_section) {
        print ""
        print "[" section "]"
        print key " = " value
      } else if (in_section && !set_key) {
        print key " = " value
      }
    }
  ' "$file" > "$tmp"
  cat "$tmp" > "$file"
  rm -f "$tmp"
}

DEFAULT_API="https://api.admodum.com.au"
DEFAULT_SERVER_URL="https://files.admodum.com.au"
DEFAULT_CLOUD_HOST="mqtt.admodum.com.au"
DEFAULT_AUTH_TENANT="bettergrow"
DEFAULT_ADMIN_EMAIL="admin@bettergrow.com"
DEFAULT_ADMIN_PASSWORD="password123"
DEFAULT_TENANT_SLUG="bettergrow"
DEFAULT_DEVICE_TYPE_SLUG="gateway"
DEFAULT_DEVICE_TYPE_NAME="Gateway"

SKIP_BIN_DOWNLOAD="${SKIP_BIN_DOWNLOAD:-0}"
DEVICE_ID="${DEVICE_ID:-}"
PTOKEN="${PTOKEN:-}"
API="${API:-$DEFAULT_API}"
SERVER_URL="${SERVER_URL:-$DEFAULT_SERVER_URL}"
BIN_URL="${BIN_URL:-$SERVER_URL/bettergrow-embed}"
BIN_PATH="${BIN_PATH:-/usr/local/bin/bettergrow-embed}"
SERVICE_URL="${SERVICE_URL:-$SERVER_URL/bettergrow-embed.service}"
CONFIG_URL="${CONFIG_URL:-$SERVER_URL/default-config.toml}"
CLOUD_HOST="${CLOUD_HOST:-$DEFAULT_CLOUD_HOST}"
CLOUD_PORT="${CLOUD_PORT:-8883}"
AUTH_TENANT="${AUTH_TENANT:-$DEFAULT_AUTH_TENANT}"
ADMIN_EMAIL="${ADMIN_EMAIL:-$DEFAULT_ADMIN_EMAIL}"
ADMIN_PASSWORD="${ADMIN_PASSWORD:-$DEFAULT_ADMIN_PASSWORD}"
TENANT_SLUG="${TENANT_SLUG:-$DEFAULT_TENANT_SLUG}"
TENANT_ID="${TENANT_ID:-}"
DEVICE_NAME="${DEVICE_NAME:-}"
DEVICE_TYPE_SLUG="${DEVICE_TYPE_SLUG:-$DEFAULT_DEVICE_TYPE_SLUG}"
DEVICE_TYPE_NAME="${DEVICE_TYPE_NAME:-$DEFAULT_DEVICE_TYPE_NAME}"
DEVICE_TYPE_ID="${DEVICE_TYPE_ID:-}"
ADMIN_TOKEN="${ADMIN_TOKEN:-}"
CONFIG_PATH="${CONFIG_PATH:-/home/delphi/.config/bettergrow-embed/config.toml}"
CERT_DIR="${CERT_DIR:-/etc/delphi}"
SCRIPT_PATH="${BASH_SOURCE[0]:-}"
SCRIPT_DIR=""
REPO_ROOT=""
if [[ -n "$SCRIPT_PATH" && -f "$SCRIPT_PATH" ]]; then
  SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
  REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
fi

while [[ "$#" -gt 0 ]]; do
  case "$1" in
    --device-id)
      DEVICE_ID="${2:-}"
      shift 2
      ;;
    --token)
      PTOKEN="${2:-}"
      shift 2
      ;;
    --api)
      API="${2:-}"
      shift 2
      ;;
    --server-url)
      SERVER_URL="${2:-}"
      shift 2
      ;;
    --skip-bin-download)
      SKIP_BIN_DOWNLOAD=1
      shift
      ;;
    --bin-url)
      BIN_URL="${2:-}"
      shift 2
      ;;
    --bin-path)
      BIN_PATH="${2:-}"
      shift 2
      ;;
    --service-url)
      SERVICE_URL="${2:-}"
      shift 2
      ;;
    --config-url)
      CONFIG_URL="${2:-}"
      shift 2
      ;;
    --cloud-host)
      CLOUD_HOST="${2:-}"
      shift 2
      ;;
    --cloud-port)
      CLOUD_PORT="${2:-}"
      shift 2
      ;;
    --auth-tenant)
      AUTH_TENANT="${2:-}"
      shift 2
      ;;
    --admin-email)
      ADMIN_EMAIL="${2:-}"
      shift 2
      ;;
    --admin-password)
      ADMIN_PASSWORD="${2:-}"
      shift 2
      ;;
    --tenant-slug)
      TENANT_SLUG="${2:-}"
      shift 2
      ;;
    --tenant-id)
      TENANT_ID="${2:-}"
      shift 2
      ;;
    --device-name)
      DEVICE_NAME="${2:-}"
      shift 2
      ;;
    --device-type-slug)
      DEVICE_TYPE_SLUG="${2:-}"
      shift 2
      ;;
    --device-type-name)
      DEVICE_TYPE_NAME="${2:-}"
      shift 2
      ;;
    --device-type-id)
      DEVICE_TYPE_ID="${2:-}"
      shift 2
      ;;
    --config)
      CONFIG_PATH="${2:-}"
      shift 2
      ;;
    --cert-dir)
      CERT_DIR="${2:-}"
      shift 2
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      usage >&2
      die "unknown argument: $1"
      ;;
  esac
done

[[ -n "$API" ]] || die "API is required"
[[ -n "$CLOUD_HOST" ]] || die "CLOUD_HOST is required"
[[ "$CLOUD_PORT" =~ ^[0-9]+$ ]] || die "CLOUD_PORT must be numeric"
API="${API%/}"

require_root
require_command curl
require_command jq
require_command openssl
require_command awk
require_command mktemp

CURL_OPTS=(--fail --progress-bar --show-error --location --globoff \
  --connect-timeout 10 \
  --speed-time 30 --speed-limit 1024 \
  --retry 5 --retry-delay 5 --retry-all-errors)

KEY_PATH="$CERT_DIR/device.key"
CSR_PATH="$CERT_DIR/device.csr"
PROVISION_PATH="$CERT_DIR/provision.json"
CERT_PATH="$CERT_DIR/device.crt"
CA_PATH="$CERT_DIR/ca.crt"
DEVICE_ID_PATH="$CERT_DIR/device.id"
SERVICE_PATH="/etc/systemd/system/bettergrow-embed.service"

log "Starting device setup"
echo "API:         $API"
echo "Binary URL:  $BIN_URL"
echo "Binary path: $BIN_PATH"
echo "Service URL: $SERVICE_URL"
echo "Config URL:  $CONFIG_URL"
echo "Cloud host:  $CLOUD_HOST"
echo "Cloud port:  $CLOUD_PORT"
echo "Cert dir:    $CERT_DIR"
echo "Config path: $CONFIG_PATH"

install -d -m 0755 "$CERT_DIR"

if [[ "$SKIP_BIN_DOWNLOAD" == "1" ]]; then
  log "Skipping BetterGrow embed binary download (SKIP_BIN_DOWNLOAD=1)"
  if [[ ! -x "$BIN_PATH" ]]; then
    warn "binary not present at $BIN_PATH; service will fail to start"
  else
    ls -lh "$BIN_PATH"
  fi
else
  log "Installing BetterGrow embed binary"
  install -d -m 0755 "$(dirname "$BIN_PATH")"
  curl --fail --show-error --location --globoff \
    --connect-timeout 10 \
    --speed-time 30 --speed-limit 1024 \
    --retry 5 --retry-delay 5 --retry-all-errors \
    -C - --progress-bar \
    "$BIN_URL" -o "$BIN_PATH"
  chmod 0755 "$BIN_PATH"
  ls -lh "$BIN_PATH"
  file "$BIN_PATH" || true
fi

admin_headers=()

ensure_admin_token() {
  if [[ -n "$ADMIN_TOKEN" ]]; then
    echo "Using ADMIN_TOKEN from environment"
    return
  fi

  [[ -n "$AUTH_TENANT" ]] || die "AUTH_TENANT is required"
  [[ -n "$ADMIN_EMAIL" ]] || die "ADMIN_EMAIL is required"
  [[ -n "$ADMIN_PASSWORD" ]] || die "ADMIN_PASSWORD is required"

  log "Logging in to API"
  local login_json
  login_json="$(jq -n \
    --arg tenant "$AUTH_TENANT" \
    --arg email "$ADMIN_EMAIL" \
    --arg password "$ADMIN_PASSWORD" \
    '{tenant:$tenant,email:$email,password:$password}')"

  ADMIN_TOKEN="$(curl "${CURL_OPTS[@]}" "$API/auth/login" \
    -H "content-type: application/json" \
    -d "$login_json" \
    | jq -er '.access_token')"
}

resolve_platform_tenant() {
  if [[ "$AUTH_TENANT" != "platform" ]]; then
    return
  fi

  if [[ -n "$TENANT_ID" ]]; then
    echo "Using tenant id: $TENANT_ID"
    return
  fi

  [[ -n "$TENANT_SLUG" ]] || die "TENANT_SLUG is required when AUTH_TENANT=platform"

  log "Resolving tenant slug"
  TENANT_ID="$(curl "${CURL_OPTS[@]}" "$API/tenants?page[size]=100" \
    -H "authorization: Bearer $ADMIN_TOKEN" \
    | jq -er --arg slug "$TENANT_SLUG" '.data[] | select(.attributes.slug == $slug) | .id' \
    | head -n 1)"

  [[ -n "$TENANT_ID" ]] || die "could not resolve tenant slug: $TENANT_SLUG"
  echo "Resolved tenant $TENANT_SLUG: $TENANT_ID"
}

set_admin_headers() {
  admin_headers=(-H "authorization: Bearer $ADMIN_TOKEN")
  if [[ "$AUTH_TENANT" == "platform" ]]; then
    admin_headers+=(-H "x-tenant-id: $TENANT_ID")
  fi
}

default_device_name() {
  local host=""
  host="$(hostname -s 2>/dev/null || true)"
  if [[ -n "$host" ]]; then
    printf 'bg-%s' "$host"
  else
    printf 'bg-device'
  fi
}

default_device_type_schema() {
  jq -n '{
    channels: [
      "sensor_a.cell_output",
      "sensor_b.cell_output",
      "sensor_c.cell_output",
      "gps.lat",
      "gps.lon",
      "gps.alt"
    ]
  }'
}

ensure_device_type_id() {
  if [[ -n "$DEVICE_TYPE_ID" || -z "$DEVICE_TYPE_SLUG" ]]; then
    return
  fi

  log "Resolving device type"
  if DEVICE_TYPE_ID="$(curl "${CURL_OPTS[@]}" "$API/device-types?page[size]=100" \
    "${admin_headers[@]}" \
    | jq -er --arg slug "$DEVICE_TYPE_SLUG" 'first(.data[] | select(.attributes.slug == $slug) | .id) // empty')"; then
    echo "Using device type $DEVICE_TYPE_SLUG: $DEVICE_TYPE_ID"
    return
  fi

  echo "Creating device type: $DEVICE_TYPE_SLUG"
  local schema
  schema="$(default_device_type_schema)"

  local body
  body="$(jq -n \
    --arg name "$DEVICE_TYPE_NAME" \
    --arg slug "$DEVICE_TYPE_SLUG" \
    --argjson schema "$schema" \
    '{data:{attributes:{name:$name,slug:$slug,schema:$schema}}}')"

  DEVICE_TYPE_ID="$(curl "${CURL_OPTS[@]}" -X POST "$API/device-types" \
    "${admin_headers[@]}" \
    -H "content-type: application/json" \
    -d "$body" \
    | jq -er '.data.id')"

  echo "Created device type $DEVICE_TYPE_ID"
}

ensure_device_id() {
  if [[ -n "$DEVICE_ID" ]]; then
    echo "$DEVICE_ID" > "$DEVICE_ID_PATH"
    chmod 644 "$DEVICE_ID_PATH"
    echo "Using device id from input: $DEVICE_ID"
    return
  fi

  if [[ -s "$DEVICE_ID_PATH" ]]; then
    DEVICE_ID="$(tr -d '[:space:]' < "$DEVICE_ID_PATH")"
    echo "Using persisted device id: $DEVICE_ID"
    return
  fi

  if [[ -s "$CONFIG_PATH" ]]; then
    DEVICE_ID="$(awk '
      /^\[device\][[:space:]]*$/ { in_device = 1; next }
      /^\[[^]]+\][[:space:]]*$/ { in_device = 0 }
      in_device && /^[[:space:]]*id[[:space:]]*=/ {
        value = $0
        sub(/^[^=]*=[[:space:]]*/, "", value)
        gsub(/["[:space:]]/, "", value)
        print value
        exit
      }
    ' "$CONFIG_PATH")"
    if [[ -n "$DEVICE_ID" ]]; then
      echo "$DEVICE_ID" > "$DEVICE_ID_PATH"
      chmod 644 "$DEVICE_ID_PATH"
      echo "Using device id from config: $DEVICE_ID"
      return
    fi
  fi

  if [[ -s "$CERT_PATH" ]]; then
    DEVICE_ID="$(openssl x509 -in "$CERT_PATH" -noout -subject -nameopt RFC2253 2>/dev/null \
      | sed -n 's/^subject=CN=\([^,]*\).*$/\1/p')"
    if [[ -n "$DEVICE_ID" ]]; then
      echo "$DEVICE_ID" > "$DEVICE_ID_PATH"
      chmod 644 "$DEVICE_ID_PATH"
      echo "Using device id from certificate: $DEVICE_ID"
      return
    fi
  fi

  ensure_admin_token
  resolve_platform_tenant
  set_admin_headers
  ensure_device_type_id

  if [[ -z "$DEVICE_NAME" ]]; then
    DEVICE_NAME="$(default_device_name)"
  fi

  log "Creating device"
  local attrs
  if [[ -n "$DEVICE_TYPE_ID" ]]; then
    attrs="$(jq -n \
      --arg name "$DEVICE_NAME" \
      --arg device_type_id "$DEVICE_TYPE_ID" \
      '{name:$name,device_type_id:$device_type_id}')"
  else
    attrs="$(jq -n --arg name "$DEVICE_NAME" '{name:$name}')"
  fi

  local body
  body="$(jq -n --argjson attrs "$attrs" '{data:{attributes:$attrs}}')"

  DEVICE_ID="$(curl "${CURL_OPTS[@]}" -X POST "$API/devices" \
    "${admin_headers[@]}" \
    -H "content-type: application/json" \
    -d "$body" \
    | jq -er '.data.id')"

  echo "$DEVICE_ID" > "$DEVICE_ID_PATH"
  chmod 644 "$DEVICE_ID_PATH"
  echo "Created device $DEVICE_ID"
}

ensure_provisioning_token() {
  if [[ -n "$PTOKEN" ]]; then
    return
  fi

  if [[ -s "$CERT_PATH" && -s "$CA_PATH" && -s "$PROVISION_PATH" ]]; then
    return
  fi

  ensure_admin_token
  resolve_platform_tenant
  set_admin_headers

  log "Minting provisioning token"
  PTOKEN="$(curl "${CURL_OPTS[@]}" -X POST "$API/devices/$DEVICE_ID/provisioning-tokens" \
    "${admin_headers[@]}" \
    | jq -er '.token')"
}

ensure_device_id
ensure_provisioning_token
[[ -n "$DEVICE_ID" ]] || die "DEVICE_ID is required"

echo "Device ID:   $DEVICE_ID"

log "Checking device key and CSR"
if [[ -f "$KEY_PATH" ]]; then
  echo "Existing private key: $KEY_PATH"
  chmod 600 "$KEY_PATH"
else
  echo "Generating ECDSA P-256 private key: $KEY_PATH"
  openssl ecparam -name prime256v1 -genkey -noout -out "$KEY_PATH"
  chmod 600 "$KEY_PATH"
fi

echo "Generating CSR: $CSR_PATH"
openssl req -new \
  -key "$KEY_PATH" \
  -out "$CSR_PATH" \
  -subj "/CN=$DEVICE_ID"

log "Checking provisioned certificate"
if [[ -s "$CERT_PATH" && -s "$CA_PATH" && -s "$PROVISION_PATH" ]]; then
  echo "Certificate material already exists; skipping token exchange"
else
  echo "Exchanging provisioning token for certificate"
  CSR_JSON="$(jq -Rs . < "$CSR_PATH")"

  curl "${CURL_OPTS[@]}" "$API/devices/provision" \
    -H "content-type: application/json" \
    -d "{\"token\":\"$PTOKEN\",\"csr\":$CSR_JSON}" \
    > "$PROVISION_PATH"

  jq -e '.cert and (.ca_chain | length > 0)' < "$PROVISION_PATH" >/dev/null
  jq -r '.cert' < "$PROVISION_PATH" > "$CERT_PATH"
  jq -r '.ca_chain[]' < "$PROVISION_PATH" > "$CA_PATH"
  chmod 644 "$CERT_PATH" "$CA_PATH" "$PROVISION_PATH"
fi

echo "Certificate expiry:"
jq -r '.not_after // "unknown"' < "$PROVISION_PATH" || true
echo "Renew after:"
jq -r '.renew_after // "unknown"' < "$PROVISION_PATH" || true

log "Updating bettergrow-embed config"
install -d -m 0755 "$(dirname "$CONFIG_PATH")"

if [[ ! -f "$CONFIG_PATH" ]]; then
  echo "Downloading default config: $CONFIG_URL"
  curl "${CURL_OPTS[@]}" "$CONFIG_URL" -o "$CONFIG_PATH"
fi

set_toml_value "$CONFIG_PATH" "device" "id" "$(toml_quote "$DEVICE_ID")"
set_toml_value "$CONFIG_PATH" "cloud" "host" "$(toml_quote "$CLOUD_HOST")"
set_toml_value "$CONFIG_PATH" "cloud" "port" "$CLOUD_PORT"
set_toml_value "$CONFIG_PATH" "cloud" "ca_cert" "$(toml_quote "$CA_PATH")"
set_toml_value "$CONFIG_PATH" "cloud" "client_cert" "$(toml_quote "$CERT_PATH")"
set_toml_value "$CONFIG_PATH" "cloud" "client_key" "$(toml_quote "$KEY_PATH")"

chmod 600 "$CONFIG_PATH"

echo "Updated config:"
grep -nE '^\[device\]|^\[cloud\]|^id = |^host = |^port = |^ca_cert = |^client_cert = |^client_key = ' "$CONFIG_PATH" || true

install_service_unit() {
  log "Installing systemd service"

  if ! have_command systemctl; then
    warn "systemctl not found; skipping service install"
    return
  fi

  curl "${CURL_OPTS[@]}" "$SERVICE_URL" -o "$SERVICE_PATH"
  chmod 644 "$SERVICE_PATH"
  systemctl daemon-reload
  systemctl enable bettergrow-embed
}

install_service_unit

log "Restarting service"
if have_command systemctl; then
  if systemctl list-unit-files bettergrow-embed.service >/dev/null 2>&1 || systemctl status bettergrow-embed >/dev/null 2>&1; then
    systemctl restart bettergrow-embed
    systemctl status bettergrow-embed --no-pager || true
  else
    warn "bettergrow-embed service is not installed; skipping restart"
  fi
else
  warn "systemctl not found; skipping service restart"
fi

log "Setup complete"
echo "Next check:"
echo "  journalctl -u bettergrow-embed -f"
