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

require_cmd() {
	if ! command -v "$1" >/dev/null 2>&1; then
		echo "error: missing required command: $1" >&2
		exit 1
	fi
}

require_cmd curl
require_cmd jq

usage() {
	cat <<'EOF'
Usage:
  API_KEY=... DOMAIN=example.com ./dns-desired-state-sync-example.sh desired-state.json

Environment:
  API_KEY    Required. regfish API key.
  DOMAIN     Optional. DNS zone to reconcile. Default: example.com
  BASE_URL   Optional. API base URL. Default: https://api.regfish.com
  APPLY      Optional. Set to 1 to execute changes. Default: 0 (dry-run)

Example desired-state.json:
[
  {
    "type": "A",
    "name": "api.example.com.",
    "data": "203.0.113.20",
    "ttl": 300,
    "annotation": "managed-by=dns-desired-state-sync-example"
  },
  {
    "type": "CNAME",
    "name": "www.example.com.",
    "data": "example.com.",
    "ttl": 300,
    "annotation": "managed-by=dns-desired-state-sync-example"
  },
  {
    "type": "TXT",
    "name": "_service.example.com.",
    "data": "managed-by=regfish-api",
    "ttl": 300,
    "annotation": "managed-by=dns-desired-state-sync-example"
  }
]
EOF
}

API_KEY="${API_KEY:-}"
DOMAIN="${DOMAIN:-example.com}"
BASE_URL="${BASE_URL:-https://api.regfish.com}"
APPLY="${APPLY:-0}"
MANAGED_ANNOTATION="managed-by=dns-desired-state-sync-example"
DESIRED_STATE_FILE="${1:-}"

if [[ "${DESIRED_STATE_FILE}" == "--help" || "${DESIRED_STATE_FILE}" == "-h" ]]; then
	usage
	exit 0
fi

if [[ -z "$DESIRED_STATE_FILE" ]]; then
	echo "error: missing desired-state JSON file" >&2
	echo >&2
	usage >&2
	exit 1
fi

if [[ ! -f "$DESIRED_STATE_FILE" ]]; then
	echo "error: desired-state file not found: $DESIRED_STATE_FILE" >&2
	exit 1
fi

if [[ -z "$API_KEY" ]]; then
	echo "error: set API_KEY before running this script" >&2
	echo "example: API_KEY=... DOMAIN=example.com ./dns-desired-state-sync-example.sh desired-state.json" >&2
	exit 1
fi

DESIRED_STATE="$(jq -ce 'if type == "array" then . else error("desired-state file must contain a JSON array") end' "$DESIRED_STATE_FILE")"

echo "Fetching current DNS records for ${DOMAIN}..."
CURRENT_STATE="$(
	curl \
		--silent \
		--show-error \
		--fail \
		--request GET \
		--url "${BASE_URL}/dns/${DOMAIN}/rr" \
		--header "x-api-key: ${API_KEY}"
)"

echo "Desired state:"
echo "$DESIRED_STATE" | jq .
echo

create_count=0
update_count=0
unchanged_count=0
delete_count=0

api_call() {
	local method="$1"
	local path="$2"
	local payload="${3:-}"

	if [[ "$APPLY" != "1" ]]; then
		echo "[dry-run] ${method} ${path}"
		if [[ -n "$payload" ]]; then
			echo "$payload" | jq .
		fi
		echo
		return 0
	fi

	local curl_args=(
		--silent
		--show-error
		--fail
		--request "$method"
		--url "${BASE_URL}${path}"
		--header "x-api-key: ${API_KEY}"
	)

	if [[ -n "$payload" ]]; then
		curl_args+=(
			--header "content-type: application/json"
			--data "$payload"
		)
	fi

	curl "${curl_args[@]}"
	echo
}

while IFS= read -r desired; do
	name="$(jq -r '.name' <<<"$desired")"
	type="$(jq -r '.type' <<<"$desired")"
	current="$(
		jq -c \
			--arg name "$name" \
			--arg type "$type" \
			'first((.response // [])[] | select(.name == $name and .type == $type)) // empty' \
			<<<"$CURRENT_STATE"
	)"

	if [[ -z "$current" ]]; then
		create_count=$((create_count + 1))
		echo "Create missing record: ${type} ${name}"
		api_call "POST" "/dns/rr" "$desired"
		continue
	fi

	current_id="$(jq -r '.id' <<<"$current")"
	current_cmp="$(
		jq -c \
			'{type, name, data, ttl: (.ttl // null), annotation: (.annotation // null)}' \
			<<<"$current"
	)"
	desired_cmp="$(
		jq -c \
			'{type, name, data, ttl: (.ttl // null), annotation: (.annotation // null)}' \
			<<<"$desired"
	)"

	if [[ "$current_cmp" == "$desired_cmp" ]]; then
		unchanged_count=$((unchanged_count + 1))
		echo "No change needed: ${type} ${name}"
		continue
	fi

	update_count=$((update_count + 1))
	echo "Update drifted record: ${type} ${name} (rrid=${current_id})"
	api_call "PATCH" "/dns/rr/${current_id}" "$desired"
done < <(jq -c '.[]' <<<"$DESIRED_STATE")

while IFS= read -r obsolete; do
	obsolete_id="$(jq -r '.id' <<<"$obsolete")"
	obsolete_type="$(jq -r '.type' <<<"$obsolete")"
	obsolete_name="$(jq -r '.name' <<<"$obsolete")"
	delete_count=$((delete_count + 1))
	echo "Delete obsolete managed record: ${obsolete_type} ${obsolete_name} (rrid=${obsolete_id})"
	api_call "DELETE" "/dns/rr/${obsolete_id}"
done < <(
	jq -c \
		--argjson desired "$DESIRED_STATE" \
		--arg annotation "$MANAGED_ANNOTATION" \
		'(.response // [])[]
		| select((.annotation // "") == $annotation)
		| . as $current
		| select(
			([$desired[] | (.name + "|" + .type)] | index($current.name + "|" + $current.type)) | not
		)' \
		<<<"$CURRENT_STATE"
)

echo
echo "Summary:"
echo "  create:    ${create_count}"
echo "  update:    ${update_count}"
echo "  unchanged: ${unchanged_count}"
echo "  delete:    ${delete_count}"

if [[ "$APPLY" != "1" ]]; then
	echo
	echo "Dry-run only. Re-run with APPLY=1 to execute the planned changes."
fi
