#!/usr/bin/env sh
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0

set -eu

ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT_DIR/docker/.env"
TOKENS_ENV_FILE="$ROOT_DIR/docker/.env.tokens"
COMPOSE_FILE="$ROOT_DIR/docker/docker-compose.yaml"

HOST=${MG_PUBLIC_HOST:-}
EMAIL=${MG_LETSENCRYPT_EMAIL:-}
LETSENCRYPT_ENABLED=${MG_LETSENCRYPT_ENABLED:-true}
STAGING=${MG_LETSENCRYPT_STAGING:-false}
FORCE_RENEWAL=${MG_LETSENCRYPT_FORCE_RENEWAL:-false}
PROJECT=${DOCKER_PROJECT:-magistrala}
TIMEOUT_SECONDS=${MG_LETSENCRYPT_TIMEOUT_SECONDS:-180}

usage() {
	cat <<EOF
Usage:
  MG_PUBLIC_HOST=example.com MG_LETSENCRYPT_EMAIL=admin@example.com $0
  MG_PUBLIC_HOST=example.com MG_LETSENCRYPT_EMAIL=admin@example.com MG_LETSENCRYPT_STAGING=true $0
  MG_PUBLIC_HOST=example.com MG_LETSENCRYPT_ENABLED=false $0

Required:
  MG_PUBLIC_HOST             Public DNS name that points to this Docker host.
  MG_LETSENCRYPT_EMAIL       Email address for Let's Encrypt notices when
                             MG_LETSENCRYPT_ENABLED=true.

Optional:
  MG_LETSENCRYPT_ENABLED     true by default. Set false to use the fallback
                             Nginx certificate and comment out Let's Encrypt
                             cert/key paths in docker/.env.
  MG_LETSENCRYPT_STAGING     false by default (production certs). Set true for
                             Let's Encrypt staging (testing only).
  MG_LETSENCRYPT_FORCE_RENEWAL
                             false by default. Set true to replace an existing cert.
  DOCKER_PROJECT             Compose project name. Defaults to magistrala.
  MG_LETSENCRYPT_TIMEOUT_SECONDS
                             Wait time for certificate files. Defaults to 180.
EOF
}

if [ -z "$HOST" ]; then
	usage >&2
	exit 2
fi

case "$LETSENCRYPT_ENABLED" in
	true|false)
		;;
	*)
		echo "MG_LETSENCRYPT_ENABLED must be true or false." >&2
		exit 2
		;;
esac

if [ "$LETSENCRYPT_ENABLED" = "true" ] && [ -z "$EMAIL" ]; then
	usage >&2
	exit 2
fi

if [ "$LETSENCRYPT_ENABLED" = "true" ] && [ "$HOST" = "localhost" ]; then
	echo "MG_PUBLIC_HOST must be a public DNS name, not localhost." >&2
	exit 2
fi

if [ ! -f "$ENV_FILE" ]; then
	echo "Missing $ENV_FILE" >&2
	exit 1
fi

set_env() {
	key=$1
	value=$2
	tmp=$(mktemp)
	awk -v key="$key" -v value="$value" '
		BEGIN { done = 0 }
		!done && (index($0, key "=") == 1 || index($0, "#" key "=") == 1) {
			print key "=" value
			done = 1
			next
		}
		{ print }
		END {
			if (!done) {
				print key "=" value
			}
		}
	' "$ENV_FILE" > "$tmp"
	mv "$tmp" "$ENV_FILE"
}

comment_env() {
	key=$1
	value=$2
	tmp=$(mktemp)
	awk -v key="$key" -v value="$value" '
		BEGIN { done = 0 }
		!done && (index($0, key "=") == 1 || index($0, "# " key "=") == 1 || index($0, "#" key "=") == 1) {
			print "# " key "=" value
			done = 1
			next
		}
		{ print }
		END {
			if (!done) {
				print "# " key "=" value
			}
		}
	' "$ENV_FILE" > "$tmp"
	mv "$tmp" "$ENV_FILE"
}

comment_env_any() {
	key=$1
	tmp=$(mktemp)
	awk -v key="$key" '
		index($0, key "=") == 1 { print "# " $0; next }
		{ print }
	' "$ENV_FILE" > "$tmp"
	mv "$tmp" "$ENV_FILE"
}

compose() {
	if [ -f "$TOKENS_ENV_FILE" ]; then
		docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" --env-file "$TOKENS_ENV_FILE" -p "$PROJECT" "$@"
	else
		docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" -p "$PROJECT" "$@"
	fi
}

write_ui_proxy() {
	ui_host=${MG_UI_HOST:-ui}
	cat > "$ROOT_DIR/docker/nginx/snippets/ui-proxy.conf" <<NGINX
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# Written by docker/setup-tls.sh – do not edit manually.

location / {
    if (\$scheme = http) {
        return 301 https://\$host\$request_uri;
    }
    include snippets/proxy-headers.conf;
    include snippets/ws-upgrade.conf;
    proxy_pass http://${ui_host}:3000;
}
NGINX
}

wait_for_nginx_http() {
	if ! command -v curl >/dev/null 2>&1; then
		return 0
	fi

	elapsed=0
	while [ "$elapsed" -lt "$TIMEOUT_SECONDS" ]; do
		status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 2 \
			http://127.0.0.1/.well-known/acme-challenge/magistrala-tls-probe 2>/dev/null || true)
		case "$status" in
			200|301|302|307|308|404)
				return 0
				;;
		esac
		sleep 2
		elapsed=$((elapsed + 2))
	done

	echo "Timed out waiting for Nginx to accept HTTP traffic." >&2
	compose logs --tail 80 nginx >&2 || true
	exit 1
}

cert_path="./ssl/letsencrypt/live/$HOST/fullchain.pem"
key_path="./ssl/letsencrypt/live/$HOST/privkey.pem"
cert_file="$ROOT_DIR/docker/ssl/letsencrypt/live/$HOST/fullchain.pem"
key_file="$ROOT_DIR/docker/ssl/letsencrypt/live/$HOST/privkey.pem"

if [ "$LETSENCRYPT_ENABLED" = "false" ]; then
	FORCE_RENEWAL=false
fi

if [ "$LETSENCRYPT_ENABLED" = "true" ] && [ "$STAGING" = "false" ]; then
	live_dir="$ROOT_DIR/docker/ssl/letsencrypt/live/$HOST"
	if [ -d "$live_dir" ]; then
		needs_cleanup=false
		if openssl x509 -in "$cert_file" -noout -issuer 2>/dev/null | grep -q "STAGING"; then
			echo "Existing staging certificate detected; replacing with a production certificate."
			needs_cleanup=true
		elif [ ! -L "$cert_file" ] || [ ! -L "$key_file" ]; then
			echo "Broken certificate symlinks detected; removing stale data for a fresh issuance."
			needs_cleanup=true
		fi
		if [ "$needs_cleanup" = "true" ]; then
			FORCE_RENEWAL=true
			rm -rf "$live_dir" \
				"$ROOT_DIR/docker/ssl/letsencrypt/archive/$HOST" \
				"$ROOT_DIR/docker/ssl/letsencrypt/renewal/$HOST.conf"
		fi
	fi
fi

echo "Configuring docker/.env for $HOST"
set_env MG_RELEASE_TAG latest
set_env MG_PUBLIC_HOST "$HOST"
set_env MG_UI_HOST "${MG_UI_HOST:-ui}"
set_env MG_LETSENCRYPT_ENABLED "$LETSENCRYPT_ENABLED"
set_env MG_LETSENCRYPT_EMAIL "$EMAIL"
set_env MG_LETSENCRYPT_STAGING "$STAGING"
set_env MG_LETSENCRYPT_FORCE_RENEWAL "$FORCE_RENEWAL"
set_env MG_NGINX_SERVER_NAME "$HOST"
comment_env_any MG_NGINX_SERVER_CERT
comment_env_any MG_NGINX_SERVER_KEY

set_env MG_OAUTH_UI_REDIRECT_URL "https://$HOST/api/auth/token"
set_env MG_OAUTH_UI_ERROR_URL "https://$HOST/login"
set_env MG_PASSWORD_RESET_URL_PREFIX "https://$HOST/password-reset"
set_env MG_VERIFICATION_URL_PREFIX "https://$HOST/verify-email"
set_env MG_GOOGLE_REDIRECT_URL "https://$HOST/oauth/callback/google"
set_env NEXTAUTH_URL "https://$HOST"
set_env MG_HOST_URL "https://$HOST"
set_env MG_UI_BASEURL "https://$HOST"
set_env MG_UI_CLI_MQTT_HOST "$HOST"
set_env MG_UI_CLI_WS_URL "wss://$HOST/mqtt"
set_env MG_UI_CLI_COAP_HOST "$HOST"
set_env MG_UI_CLI_HTTP_URL "https://$HOST/http"

mkdir -p "$ROOT_DIR/docker/ssl/letsencrypt" "$ROOT_DIR/docker/ssl/certbot-www"

write_ui_proxy

if [ "$LETSENCRYPT_ENABLED" = "false" ]; then
	echo "Starting Magistrala with the fallback Nginx certificate"
	compose up -d
	COMPOSE_PROFILES=letsencrypt compose stop certbot >/dev/null 2>&1 || true
	echo "Let's Encrypt disabled. Nginx cert/key paths are commented in docker/.env."
	echo "Fallback TLS setup complete: https://$HOST/"
	exit 0
fi

echo "Starting Magistrala with the fallback Nginx certificate"
compose up -d
wait_for_nginx_http

echo "Requesting Let's Encrypt certificate for $HOST"
COMPOSE_PROFILES=letsencrypt compose up -d --force-recreate certbot

cert_ready() {
	compose logs certbot 2>&1 | \
		grep -qE "Successfully received certificate|Certificate not yet due for renewal"
}

elapsed=0
until cert_ready || [ "$elapsed" -ge "$TIMEOUT_SECONDS" ]; do
	compose logs --tail 3 certbot 2>&1 | sed 's/^/  [certbot] /'
	sleep 5
	elapsed=$((elapsed + 5))
done

if ! cert_ready; then
	echo "Timed out waiting for Let's Encrypt certificate." >&2
	compose logs --tail 80 certbot >&2 || true
	exit 1
fi

echo "Certificate obtained. Switching Nginx to the issued certificate."
set_env MG_NGINX_SERVER_CERT "$cert_path"
set_env MG_NGINX_SERVER_KEY "$key_path"
set_env MG_LETSENCRYPT_FORCE_RENEWAL false

compose up -d --force-recreate nginx

echo "TLS setup complete: https://$HOST/"
