#!/usr/bin/env bash

quiet=0 # should i shut up?!?!? :<

token="" # the api token
domain="" # the domain to update

record="A" # the record type
ttl="60" # the new time to live
content="" # the new record content

usage() {
	printf "usage: cfdns [-hq] [-d DOMAIN] [-t TOKEN] [-r RECORD] [-l TTL] [-cC CONTENT]\n\n"
	printf "\t-h\t\tshow the help message\n"
	printf "\t-q\t\tquiet output\n"
	printf "\t-d DOMAIN\tthe domain name to update\n"
	printf "\t-t TOKEN\tthe api token\n"
	printf "\t-r RECORD\tthe new record type\n"
	printf "\t-l TTL\t\tthe new time to live\n"
	printf "\t-c CONTENT\tthe new contents of the record\n"
	printf "\t-C RAW CONTENT\tthe new conents of the record (as raw json)\n"
}

chk_command() {
	if ! command -v "$1" > /dev/null; then
		>&2 echo "error: command '$1' could not be found"
		exit 1
	fi
}

chk_command "getopts"
chk_command "curl"
chk_command "jq"

while getopts "hqd:t:r:l:c:C:" arg > /dev/null; do
	case $arg in
		h)
			usage
			exit 0
			;;
		q)
			quiet=1
			;;
		d)
			domain="${OPTARG}"
			;;
		t)
			token="${OPTARG}"
			;;
		r)
			record="${OPTARG}"
			;;
		l)
			ttl="${OPTARG}"
			;;
		c)
			content="\"content\": \"${OPTARG}\""
			;;
		C)
			content="${OPTARG}"
			;;
		?)
			exit 1
			;;
	esac
done

if [ ! "$cert" = "cert.pem" ] && [ "$key" = "cert.key" ]; then
	name=${cert%.*}
	key="$name.key"
fi

log() {
	if [ $quiet = 0 ]; then
		printf "$*\n" 1>&2
	fi
}

step() {
	if [ $quiet = 0 ]; then
		printf "\033[32m>>> \033[0m$*\n" 1>&2
	fi
}

error() {
	printf "\033[31merror: \033[0m$*\n" > /dev/stderr
}

die() {
        kill $$
}

handle_error() {
	errs="$1"
	count="$(echo $errs | jq -r ". | length")"
	for i in $(seq 0 $(($count - 1))); do
		err="$(echo $errs | jq -r ".[$i]")"
		error "$(echo $err | jq -r '.message')"
		chain="$(echo $err | jq -r '.error_chain')"
		if ! [ "$chain" = "null" ]; then
			count="$(echo $chain | jq -r ". | length")"
			for j in $(seq 0 $(($count - 1))); do
				error "$(echo $chain | jq -r ".[$j].message")"
			done
		fi
	done
}

_curl() {
    RESPONSE=$(curl --silent --request $1 \
    --url "https://api.cloudflare.com/client/v4$2" \
    --header "Content-Type: application/json" \
    --header "Authorization: Bearer $token" \
    --data "$3")
    SUCCESS=$(echo $RESPONSE | jq -r ".success")
    if [ "$SUCCESS" != "true" ]; then
	errs="$(echo $RESPONSE | jq -r .errors)"
	handle_error "$errs"
	die
    fi

    echo $RESPONSE | jq ".result"
}

patch() {
    _curl "PATCH" "$1" "$2"
}

post() {
    _curl "POST" "$1" "$2"
}

get() {
    _curl "GET" "$1" ""
}

# Step 1: get the zone the domain is in
zone=$(echo "$domain" | awk -F'.' '{print $(NF-1)"."$(NF);}' 2>/dev/null)

if [ "$zone" = "" ]; then
	error "invalid domain: '$domain'"
	die
fi

step "fetching zones..."
zones=$(get "/zones")
zone_id="null"

i=0
while true; do
	json="$(echo "$zones" | jq ".[$i]")"

	if [ "$json" == "null" ]; then
		break
	fi

	name="$(echo "$json" | jq -r ".name")"
	id="$(echo "$json" | jq -r ".id")"

	log "found zone: $name ($id)"

	if [ "$name" == "$zone" ]; then
		zone_id="$id"
		break
	fi

	((i=i+1))
done

if [ "$zone_id" == "null" ]; then
	error "could not find zone: $zone"
	die
fi

# Step 2: get the current record id if it exists
step "fetching records..."

records="$(get "/zones/$zone_id/dns_records")"
current="null"

i=0
while true; do

	_record="$(echo "$records" | jq ".[$i]")"

	if [ "$_record" == "null" ]; then
		break
	fi

	name="$(echo "$_record" | jq -r ".name")"
	type="$(echo "$_record" | jq -r ".type")"
	_content="$(echo "$_record" | jq -r ".name")"
	id="$(echo "$_record" | jq -r ".id")"

	log "found record: $name $type \"$_content\" ($id)"

	if [ "$name" == "$domain" ] && [ "$record" = "$type" ]; then
		current="$id"
		break
	fi

	((i=i+1))
done

# Step 3: update record
step "updating record"

update() {
    patch "/zones/$zone_id/dns_records/$1" "$2"
}

create() {
    post "/zones/$zone_id/dns_records" "$1"
}

json="{
	\"name\": \"$domain\",
	\"proxied\": false,
	\"type\": \"$record\",
	$content
}"

log $(echo "$json" | jq -C)

if [ "$current" == "null" ]; then
	log "no record found... creating new one"
	create "$json"
else
	log "record found... updating"
	update "$current" "$json"
fi

step "successfully updated record"