#!/usr/bin/env bash

quiet=false
dryrun=false

print_usage() {
    echo '"-t": test (dry-run) flag, display what would be updated, without committing the change'
    echo '"-q": quiet flag, to suppress "already set to" messages'
    echo '"-f": query filters, see: https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-list-dns-records#Query-Parameters'
    echo '"-c": create record, if no records match our query, we can create one, takes json object of record to make, see: https://developers.cloudflare.com/api/resources/dns/subresources/records/models/a_record/#(schema)'
    echo 'when using regular expressions, use "\1" for first capture group, no look-forward or look-behind, nor non-greedy wildcards'
    echo 'content can use the following tags: <a> <a:example.com> <aaaa> and <aaaa:example.com> which will be replaced with equivalent ip address, each domain tag can be used once'
    echo "Usage: [token=<token-config-override>] [zone=<zone-config-override>] [prefix=<filter-prefix-override>] $0 [-q] [-f 'query-filters'] [-c 'create-record-json'] ('content' | 'regex-replace' 'regex-find')"
}

# known private ranges
local4='192.168.0.0/16 172.16.0.0/12 10.0.0.0/8'
local6='fc00::/7 fe80::/10'

# handle all arguments provided
while getopts 'c:f:qt' flag; do
    case "$flag" in
        c)
            newRecord="$OPTARG"
            ;;
        f)
            filters="$OPTARG"
            ;;
        q)
            quiet=true
            ;;
        t)
            dryrun=true
            ;;
        *)
            echo "unknown option provided '$flag'"
            print_usage
            exit 1
            ;;
    esac
done
shift "$((OPTIND-1))"

content="$1"
regex="$2"

# load in defaults from config
scriptRoot=$(dirname "$0")
configFile="$scriptRoot/config.sh"
if [[ -f "$configFile" ]]; then
    source "$configFile"

    # you can have a prefix in your config, and override it for a specific call
    if [[ -z "$prefix" ]]; then
        prefix="$CLOUDFLARE_FILTER_PREFIX"
    fi
    if [[ -n "$prefix" ]]; then
        filters="$prefix$filters"
    fi
    if [[ -z "$zone" ]]; then
        zone="$CLOUDFLARE_ZONE"
    fi
    if [[ -z "$token" ]]; then
        token="$CLOUDFLARE_TOKEN"
    fi
fi

# stop if we do not have the required information
if [ -z "$zone" -o -z "$content" -o -z "$token" ]; then
    echo 'token, zone, and content are all required'
    print_usage
    exit 1
fi

# --- update content ---
# "<a>" becomes current public ipv4
if [[ "$content" = *'<a>'* ]]; then
    ipv4=$("$scriptRoot/4-public-ip.sh")
    content=$(echo "$content" | sed "s/<a>/$ipv4/g")

    # check if address is actually public
    if [[ -n $(command -v "grepcidr") ]]; then
        if [[ -z $(echo "$ipv4" | grepcidr -v "$local4") ]]; then
            echo "IPv4 lookup failed, stopping"
            exit 2
        fi
    fi
fi

# limit 1
# "<a:example.com>" becomes first ipv4 of given domain
if [[ "$content" = *'<a:'* ]]; then
    domain=$(echo "$content" | grep -oP '(?<=\<a:)[^>]+(?=\>)')
    ipv4=$(dig +short A "$domain" | head -n 1)
    content=$(echo "$content" | sed "s/<a:$domain>/$ipv4/g")

    # check if address is actually public
    if [[ -n $(command -v "grepcidr") ]]; then
        if [[ -z $(echo "$ipv4" | grepcidr -v "$local4") ]]; then
            echo "IPv4 lookup for '$domain' failed, stopping"
            exit 2
        fi
    fi
fi

# "<aaaa>" becomes current public ipv6
if [[ "$content" = *'<aaaa>'* ]]; then
    ipv6=$("$scriptRoot/6-public-ip.sh")
    content=$(echo "$content" | sed "s/<aaaa>/$ipv6/g")

    # check if address is actually public
    if [[ -n $(command -v "grepcidr") ]]; then
        if [[ -z $(echo "$ipv6" | grepcidr -v "$local6") ]]; then
            echo "IPv6 lookup failed, stopping"
            exit 2
        fi
    fi
fi

# limit 1
# "<aaaa:example.com>" becomes first ipv6 of given domain
if [[ "$content" = *'<aaaa:'* ]]; then
    domain=$(echo "$content" | grep -oP '(?<=\<aaaa:)[^>]+(?=\>)')
    ipv6=$(dig +short AAAA "$domain" | head -n 1)
    content=$(echo "$content" | sed "s/<aaaa:$domain>/$ipv6/g")

    # check if address is actually public
    if [[ -n $(command -v "grepcidr") ]]; then
        if [[ -z $(echo "$ipv6" | grepcidr -v "$local6") ]]; then
            echo "IPv6 lookup for '$domain' failed, stopping"
            exit 2
        fi
    fi
fi

# --- update regex ---
# "<a>" becomes current public ipv4
if [[ "$regex" = *'<a>'* ]]; then
    ipv4=$("$scriptRoot/4-public-ip.sh")
    regex=$(echo "$regex" | sed "s/<a>/$ipv4/g")
fi

# limit 1
# "<a:example.com>" becomes first ipv4 of given domain
if [[ "$regex" = *'<a:'* ]]; then
    domain=$(echo "$regex" | grep -oP '(?<=\<a:)[^>]+(?=\>)')
    ipv4=$(dig +short A "$domain" | head -n 1)
    regex=$(echo "$regex" | sed "s/<a:$domain>/$ipv4/g")
fi

# "<aaaa>" becomes current public ipv6
if [[ "$regex" = *'<aaaa>'* ]]; then
    ipv6=$("$scriptRoot/6-public-ip.sh")
    regex=$(echo "$regex" | sed "s/<aaaa>/$ipv6/g")
fi

# limit 1
# "<aaaa:example.com>" becomes first ipv6 of given domain
if [[ "$regex" = *'<aaaa:'* ]]; then
    domain=$(echo "$regex" | grep -oP '(?<=\<aaaa:)[^>]+(?=\>)')
    ipv6=$(dig +short AAAA "$domain" | head -n 1)
    regex=$(echo "$regex" | sed "s/<aaaa:$domain>/$ipv6/g")
fi

# --- get matching dns records ---
results=$(curl --silent --request GET \
  --url "https://api.cloudflare.com/client/v4/zones/$zone/dns_records?$filters" \
  --header 'Content-Type: application/json' \
  --header "Authorization: Bearer $token" )

if [[ -z "$regex" ]]; then
    newContent="$content"

    if [[ -z "$newContent" ]]; then
        if [[ "$quiet" = false ]]; then
            echo 'Content calculated to blank string, stopping.'
        fi
        exit 1
    fi
fi

while read -r result; do
    if [[ -z "$result" ]]; then
        if [[ -n "$newRecord" ]]; then
            create=$(curl --silent --request POST \
                --url "https://api.cloudflare.com/client/v4/zones/$zone/dns_records" \
                --header 'Content-Type: application/json' \
                --header "Authorization: Bearer $token" \
                --data "$newRecord" )

            # display if successful, and any messages
            success=$(echo "$create" | jq -r '.success,.messages[]')
            domain=$(echo "$newRecord" | jq -r '.name')
            if [[ "$success" == "true" ]]; then
                echo "created $domain record"
                exit 0
            else
                echo "failed to create $domain"
                exit 1
            fi
        else
            echo 'No DNS Records found to update, stopping.'
            exit 1
        fi
    fi
    # echo "$result" | jq -C '.'

    id=$(echo "$result" | jq -r '.id')
    oldContent=$(echo "$result" | jq -r '.content')
    type=$(echo "$result" | jq -r '.type')
    name=$(echo "$result" | jq -r '.name')
    if [[ -n "$regex" ]]; then
        newContent=$(echo "$oldContent" | sed -E "s/$regex/$content/g")

        if [[ -z "$newContent" ]]; then
            if [[ "$quiet" = false ]]; then
                echo 'Content calculated to blank string, skipping.'
            fi
            continue
        fi
        # echo "current '$oldContent'"
        # echo "find    '$regex'"
        # echo "replace '$content'"
        # echo "new     '$newContent'"
    fi

    if [[ "$newContent" = "$oldContent" ]]; then
        if [[ "$quiet" = false ]]; then
            echo "$type:$name is already set to '$newContent'"
        fi
    else
        echo "updating $type:$name"
        echo "now '$newContent'"
        echo "was '$oldContent'"


        if [[ "$dryrun" = true ]]; then
            echo 'true *actually just a dry-run*'
        elif [[ -z "$newContent" ]]; then
            echo 'false empty content has no effect, update skipped'
        else
            update=$(curl --silent --request PATCH \
                --url "https://api.cloudflare.com/client/v4/zones/$zone/dns_records/$id" \
                --header 'Content-Type: application/json' \
                --header "Authorization: Bearer $token" \
                --data "{\"content\": \"$newContent\"}" )

            # display if successful, and any messages
            echo "$update" | jq -r '.success,.messages[]'
        fi
    fi
done <<< $(echo "$results" | jq -c '.result[]')

