#!/bin/sh
# SecuBox DNS Provider Controller
# Programmatic DNS record management via provider APIs

VERSION="1.0.0"
CONFIG="dns-provider"
ADAPTER_DIR="/usr/lib/secubox/dns"
OVERRIDE_ZONE=""

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

log() { echo -e "${GREEN}[DNS]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }

uci_get() { uci -q get ${CONFIG}.$1; }

# Parse global options
while [ "${1#-}" != "$1" ]; do
	case "$1" in
		-z) OVERRIDE_ZONE="$2"; shift 2 ;;
		--zone=*) OVERRIDE_ZONE="${1#--zone=}"; shift ;;
		*) break ;;
	esac
done

# ============================================================================
# Provider Loading
# ============================================================================

load_provider() {
	local provider=$(uci_get main.provider)
	local zone=$(uci_get main.zone)

	if [ -z "$provider" ]; then
		error "No DNS provider configured. Set with: uci set dns-provider.main.provider='ovh'"
		exit 1
	fi

	if [ -z "$zone" ]; then
		error "No DNS zone configured. Set with: uci set dns-provider.main.zone='example.com'"
		exit 1
	fi

	local adapter="${ADAPTER_DIR}/${provider}.sh"
	if [ ! -f "$adapter" ]; then
		error "Unknown provider: $provider (no adapter at $adapter)"
		exit 1
	fi

	. "$adapter"
}

get_zone() {
	if [ -n "$OVERRIDE_ZONE" ]; then
		echo "$OVERRIDE_ZONE"
	else
		uci_get main.zone
	fi
}

# ============================================================================
# Commands
# ============================================================================

cmd_list() {
	load_provider
	local zone=$(get_zone)
	log "Listing records for $zone..."
	dns_list "$zone"
}

cmd_add() {
	local type="$1" subdomain="$2" target="$3" ttl="${4:-3600}"

	if [ -z "$type" ] || [ -z "$subdomain" ] || [ -z "$target" ]; then
		echo "Usage: dnsctl add <TYPE> <subdomain> <target> [ttl]"
		echo "  TYPE: A, AAAA, CNAME, TXT, MX, SRV"
		echo ""
		echo "Examples:"
		echo "  dnsctl add A myservice 1.2.3.4"
		echo "  dnsctl add CNAME www mycdn.net"
		echo "  dnsctl add TXT _acme-challenge 'validation-token'"
		return 1
	fi

	load_provider
	local zone=$(get_zone)
	log "Adding $type record: ${subdomain}.${zone} → $target (TTL: $ttl)"
	dns_add "$zone" "$type" "$subdomain" "$target" "$ttl"
	echo ""
	log "Record created. Verify with: dnsctl verify ${subdomain}.${zone}"
}

cmd_rm() {
	local type="$1" subdomain="$2"

	if [ -z "$type" ] || [ -z "$subdomain" ]; then
		echo "Usage: dnsctl rm <TYPE> <subdomain>"
		echo "  dnsctl rm A myservice"
		return 1
	fi

	load_provider
	local zone=$(get_zone)
	log "Removing $type record for ${subdomain}.${zone}..."
	dns_rm "$zone" "$type" "$subdomain"
	log "Record removed."
}

cmd_sync() {
	load_provider
	local zone=$(get_zone)
	log "Syncing local vhosts to DNS..."

	# Read HAProxy vhosts
	local idx=0
	local synced=0
	while uci -q get haproxy.@vhost[$idx] >/dev/null 2>&1; do
		local domain=$(uci -q get haproxy.@vhost[$idx].domain)
		local enabled=$(uci -q get haproxy.@vhost[$idx].enabled)

		if [ "$enabled" = "1" ] && [ -n "$domain" ]; then
			# Check if domain is within our zone
			if echo "$domain" | grep -q "\.${zone}$"; then
				local subdomain=$(echo "$domain" | sed "s/\.${zone}$//")

				# Get public IP
				local public_ip=$(curl -s --connect-timeout 5 https://ipv4.icanhazip.com 2>/dev/null | tr -d '\n')
				if [ -n "$public_ip" ]; then
					log "  $subdomain → $public_ip"
					dns_add "$zone" "A" "$subdomain" "$public_ip" 3600
					synced=$((synced + 1))
				fi
			fi
		fi
		idx=$((idx + 1))
	done

	log "Synced $synced record(s)."
}

cmd_verify() {
	local fqdn="$1"

	if [ -z "$fqdn" ]; then
		echo "Usage: dnsctl verify <fqdn>"
		return 1
	fi

	log "Checking DNS propagation for $fqdn..."

	# Check with multiple resolvers
	local resolvers="1.1.1.1 8.8.8.8 9.9.9.9"
	local resolved=0
	local failed=0

	for resolver in $resolvers; do
		local result=$(nslookup "$fqdn" "$resolver" 2>/dev/null | grep "Address:" | tail -1 | awk '{print $2}')
		if [ -n "$result" ] && [ "$result" != "$resolver" ]; then
			echo -e "  ${GREEN}$resolver${NC} → $result"
			resolved=$((resolved + 1))
		else
			echo -e "  ${RED}$resolver${NC} → not resolved"
			failed=$((failed + 1))
		fi
	done

	echo ""
	if [ "$failed" -eq 0 ]; then
		log "Fully propagated ($resolved/$((resolved + failed)) resolvers)"
	elif [ "$resolved" -gt 0 ]; then
		warn "Partially propagated ($resolved/$((resolved + failed)) resolvers)"
	else
		error "Not propagated yet. DNS changes may take up to 24 hours."
	fi
}

cmd_test() {
	load_provider
	local provider=$(uci_get main.provider)
	log "Testing $provider credentials..."
	local result=$(dns_test_credentials)
	if [ "$result" = "ok" ]; then
		log "Credentials valid."
	else
		error "Credential test failed: $result"
		return 1
	fi
}

cmd_status() {
	local provider=$(uci_get main.provider)
	local zone=$(uci_get main.zone)
	local enabled=$(uci_get main.enabled)

	echo ""
	echo "========================================"
	echo "  DNS Provider Status v$VERSION"
	echo "========================================"
	echo ""
	echo "  Enabled:   $([ "$enabled" = "1" ] && echo -e "${GREEN}Yes${NC}" || echo -e "${RED}No${NC}")"
	echo "  Provider:  ${provider:-not set}"
	echo "  Zone:      ${zone:-not set}"

	if [ -n "$provider" ]; then
		echo ""
		echo "Provider Config ($provider):"
		case "$provider" in
			ovh)
				local endpoint=$(uci_get ovh.endpoint)
				local app_key=$(uci_get ovh.app_key)
				echo "  Endpoint:    ${endpoint:-ovh-eu}"
				echo "  App Key:     ${app_key:+configured}${app_key:-NOT SET}"
				echo "  App Secret:  $([ -n "$(uci_get ovh.app_secret)" ] && echo "configured" || echo "NOT SET")"
				echo "  Consumer Key:$([ -n "$(uci_get ovh.consumer_key)" ] && echo " configured" || echo " NOT SET")"
				;;
			gandi)
				echo "  API Key:     $([ -n "$(uci_get gandi.api_key)" ] && echo "configured" || echo "NOT SET")"
				;;
			cloudflare)
				echo "  API Token:   $([ -n "$(uci_get cloudflare.api_token)" ] && echo "configured" || echo "NOT SET")"
				echo "  Zone ID:     $([ -n "$(uci_get cloudflare.zone_id)" ] && echo "configured" || echo "NOT SET")"
				;;
		esac
	fi

	echo ""
}

# ============================================================================
# DynDNS Commands
# ============================================================================

cmd_dyndns() {
	local subdomain="${1:-@}"
	local ttl="${2:-300}"

	load_provider
	local zone=$(get_zone)
	local provider=$(uci_get main.provider)

	log "DynDNS update for ${subdomain}.${zone}..."

	# Check if provider has dyndns support
	if ! type dns_dyndns >/dev/null 2>&1; then
		error "Provider $provider does not support DynDNS (dns_dyndns function missing)"
		return 1
	fi

	local result=$(dns_dyndns "$zone" "$subdomain" "$ttl")
	echo "$result"

	if echo "$result" | grep -q '"status":"updated"'; then
		local new_ip=$(echo "$result" | jsonfilter -e '@.new_ip' 2>/dev/null)
		log "Updated ${subdomain}.${zone} → $new_ip"
	elif echo "$result" | grep -q '"status":"unchanged"'; then
		local ip=$(echo "$result" | jsonfilter -e '@.ip' 2>/dev/null)
		log "IP unchanged: $ip"
	else
		error "DynDNS update failed"
		return 1
	fi
}

cmd_get() {
	local type="$1" subdomain="$2"

	if [ -z "$type" ]; then
		echo "Usage: dnsctl get <TYPE> [subdomain]"
		echo "  dnsctl get A @          # Get root A record"
		echo "  dnsctl get A www        # Get www A record"
		return 1
	fi

	[ -z "$subdomain" ] && subdomain="@"

	load_provider
	local zone=$(get_zone)

	# Check if provider has get support
	if type dns_get >/dev/null 2>&1; then
		dns_get "$zone" "$type" "$subdomain"
	else
		# Fallback to list and filter
		dns_list "$zone" | jsonfilter -e "@[*]" 2>/dev/null | \
			grep -E "\"rrset_name\":\"$subdomain\"" | \
			grep -E "\"rrset_type\":\"$type\""
	fi
}

cmd_update() {
	local type="$1" subdomain="$2" target="$3" ttl="${4:-3600}"

	if [ -z "$type" ] || [ -z "$subdomain" ] || [ -z "$target" ]; then
		echo "Usage: dnsctl update <TYPE> <subdomain> <target> [ttl]"
		echo "  Updates existing record (use 'add' for new records)"
		return 1
	fi

	load_provider
	local zone=$(get_zone)

	# Check if provider has update support
	if type dns_update >/dev/null 2>&1; then
		log "Updating $type record: ${subdomain}.${zone} → $target"
		dns_update "$zone" "$type" "$subdomain" "$target" "$ttl"
	else
		# Fallback: remove and add
		warn "Provider lacks update, using rm+add"
		dns_rm "$zone" "$type" "$subdomain"
		dns_add "$zone" "$type" "$subdomain" "$target" "$ttl"
	fi
}

cmd_domains() {
	load_provider
	local provider=$(uci_get main.provider)

	if type dns_domains >/dev/null 2>&1; then
		log "Domains in $provider account:"
		dns_domains
	else
		error "Provider $provider does not support domain listing"
		return 1
	fi
}

# ============================================================================
# Subdomain Generator
# ============================================================================

cmd_generate() {
	local service="$1"
	local prefix="${2:-}"

	if [ -z "$service" ]; then
		echo "Usage: dnsctl generate <service> [prefix]"
		echo "  Auto-generates unique subdomain and creates A record"
		echo ""
		echo "Examples:"
		echo "  dnsctl generate gitea          # Creates gitea.zone.tld"
		echo "  dnsctl generate blog dev       # Creates dev-blog.zone.tld"
		echo "  dnsctl generate api prod       # Creates prod-api.zone.tld"
		return 1
	fi

	load_provider
	local zone=$(get_zone)
	local public_ip=$(curl -s --connect-timeout 5 https://ipv4.icanhazip.com 2>/dev/null | tr -d '\n')

	[ -z "$public_ip" ] && { error "Cannot detect public IP"; return 1; }

	# Build subdomain name
	local subdomain
	if [ -n "$prefix" ]; then
		subdomain="${prefix}-${service}"
	else
		subdomain="$service"
	fi

	# Check if subdomain exists
	local existing=$(nslookup "${subdomain}.${zone}" 8.8.8.8 2>/dev/null | grep "Address:" | tail -1)
	if echo "$existing" | grep -q "[0-9]"; then
		warn "Subdomain ${subdomain}.${zone} already exists"
		# Try with numeric suffix
		local i=2
		while [ $i -lt 10 ]; do
			local try_sub="${subdomain}${i}"
			local try_check=$(nslookup "${try_sub}.${zone}" 8.8.8.8 2>/dev/null | grep "Address:" | tail -1)
			if ! echo "$try_check" | grep -q "[0-9]"; then
				subdomain="$try_sub"
				break
			fi
			i=$((i + 1))
		done
	fi

	log "Generating subdomain: ${subdomain}.${zone}"
	dns_add "$zone" "A" "$subdomain" "$public_ip" 3600

	echo ""
	echo "Created: ${subdomain}.${zone} → $public_ip"
	echo ""
	log "Verify with: dnsctl verify ${subdomain}.${zone}"
}

cmd_suggest() {
	local category="${1:-web}"

	echo ""
	echo "Subdomain suggestions for category: $category"
	echo ""

	case "$category" in
		web)
			echo "  www, blog, shop, app, portal, admin, api"
			;;
		mail)
			echo "  mail, smtp, imap, pop, webmail, mx, mta"
			;;
		dev)
			echo "  git, dev, staging, test, ci, cd, build"
			;;
		media)
			echo "  media, cdn, stream, video, music, files"
			;;
		iot)
			echo "  mqtt, home, sensor, hub, iot, zwave, zigbee"
			;;
		security)
			echo "  vpn, tor, proxy, guard, auth, sso"
			;;
		*)
			echo "  Custom: use 'dnsctl generate <name>'"
			;;
	esac
	echo ""
}

# ============================================================================
# Mail DNS Records (MX, SPF, DKIM, DMARC)
# ============================================================================

cmd_mail_setup() {
	local mail_host="${1:-mail}"
	local priority="${2:-10}"

	load_provider
	local zone=$(get_zone)
	local public_ip=$(curl -s --connect-timeout 5 https://ipv4.icanhazip.com 2>/dev/null | tr -d '\n')

	[ -z "$public_ip" ] && { error "Cannot detect public IP"; return 1; }

	log "Setting up mail DNS records for $zone..."

	# Create mail A record
	log "  Creating A record: ${mail_host}.${zone} → $public_ip"
	dns_add "$zone" "A" "$mail_host" "$public_ip" 3600

	# Create MX record
	log "  Creating MX record: ${zone} → ${mail_host}.${zone} (priority $priority)"
	dns_add "$zone" "MX" "@" "${priority} ${mail_host}.${zone}." 3600

	# Create SPF record
	local spf="v=spf1 mx a:${mail_host}.${zone} ~all"
	log "  Creating SPF record: $spf"
	dns_add "$zone" "TXT" "@" "$spf" 3600

	# Create DMARC record (relaxed policy for start)
	local dmarc="v=DMARC1; p=none; rua=mailto:postmaster@${zone}"
	log "  Creating DMARC record: $dmarc"
	dns_add "$zone" "TXT" "_dmarc" "$dmarc" 3600

	echo ""
	log "Mail DNS setup complete!"
	echo ""
	echo "Next steps:"
	echo "  1. Configure your mail server at ${mail_host}.${zone}"
	echo "  2. Generate DKIM keys and add TXT record:"
	echo "     dnsctl add TXT mail._domainkey '<dkim-public-key>'"
	echo "  3. Verify MX: dig MX ${zone}"
	echo "  4. Test SPF: dig TXT ${zone}"
	echo ""
}

cmd_dkim_add() {
	local selector="${1:-mail}"
	local public_key="$2"

	if [ -z "$public_key" ]; then
		echo "Usage: dnsctl dkim-add [selector] <public_key>"
		echo "  selector defaults to 'mail'"
		echo ""
		echo "Generate DKIM key pair:"
		echo "  openssl genrsa -out dkim.private 2048"
		echo "  openssl rsa -in dkim.private -pubout -out dkim.public"
		echo "  # Use content between -----BEGIN/END PUBLIC KEY----- (no newlines)"
		return 1
	fi

	load_provider
	local zone=$(get_zone)

	# Format DKIM record
	local dkim="v=DKIM1; k=rsa; p=${public_key}"
	log "Adding DKIM record: ${selector}._domainkey.${zone}"
	dns_add "$zone" "TXT" "${selector}._domainkey" "$dkim" 3600

	log "DKIM record added. Verify with: dig TXT ${selector}._domainkey.${zone}"
}

# ============================================================================
# ACME DNS-01 Helper
# ============================================================================

cmd_acme_dns01() {
	local domain="$1"

	if [ -z "$domain" ]; then
		echo "Usage: dnsctl acme-dns01 <domain>"
		echo "Issues certificate via DNS-01 challenge using configured provider."
		return 1
	fi

	load_provider
	local provider=$(uci_get main.provider)

	log "Issuing certificate for $domain via DNS-01 ($provider)..."

	case "$provider" in
		ovh)
			export OVH_END_POINT=$(uci_get ovh.endpoint)
			export OVH_APPLICATION_KEY=$(uci_get ovh.app_key)
			export OVH_APPLICATION_SECRET=$(uci_get ovh.app_secret)
			export OVH_CONSUMER_KEY=$(uci_get ovh.consumer_key)
			acme.sh --issue -d "$domain" --dns dns_ovh
			;;
		gandi)
			export GANDI_LIVEDNS_KEY=$(uci_get gandi.api_key)
			acme.sh --issue -d "$domain" --dns dns_gandi_livedns
			;;
		cloudflare)
			export CF_Token=$(uci_get cloudflare.api_token)
			export CF_Zone_ID=$(uci_get cloudflare.zone_id)
			acme.sh --issue -d "$domain" --dns dns_cf
			;;
		*)
			error "Unsupported provider for ACME DNS-01: $provider"
			return 1
			;;
	esac
}

# ============================================================================
# Main
# ============================================================================

show_help() {
	cat << EOF
SecuBox DNS Provider Control v$VERSION

Usage: dnsctl <command> [options]

Commands:
  list                         List all DNS records in zone
  add <TYPE> <sub> <target>    Create DNS record (A, AAAA, CNAME, TXT, MX)
  update <TYPE> <sub> <target> Update existing DNS record
  get <TYPE> [subdomain]       Get specific record value
  rm <TYPE> <subdomain>        Remove DNS record
  sync                         Sync HAProxy vhosts → DNS A records
  verify <fqdn>                Check DNS propagation across resolvers
  test                         Verify provider API credentials
  status                       Show provider configuration status
  domains                      List all domains in provider account

DynDNS:
  dyndns [subdomain] [ttl]     Update A record with current WAN IP

Generator:
  generate <service> [prefix]  Auto-generate subdomain with A record
  suggest [category]           Show subdomain name suggestions

Mail:
  mail-setup [host] [priority] Set up MX, SPF, DMARC records
  dkim-add [selector] <pubkey> Add DKIM TXT record

SSL:
  acme-dns01 <domain>          Issue SSL cert via DNS-01 challenge

Examples:
  dnsctl add A gitea 1.2.3.4
  dnsctl update A gitea 5.6.7.8
  dnsctl get A www
  dnsctl dyndns @ 300          # Update root A record
  dnsctl dyndns api 600        # Update api.zone with WAN IP
  dnsctl rm A gitea
  dnsctl sync
  dnsctl verify gitea.example.com
  dnsctl acme-dns01 '*.example.com'

EOF
}

case "${1:-}" in
	list)        shift; cmd_list "$@" ;;
	add)         shift; cmd_add "$@" ;;
	update)      shift; cmd_update "$@" ;;
	get)         shift; cmd_get "$@" ;;
	rm|remove)   shift; cmd_rm "$@" ;;
	sync)        shift; cmd_sync "$@" ;;
	verify)      shift; cmd_verify "$@" ;;
	test)        shift; cmd_test "$@" ;;
	status)      shift; cmd_status "$@" ;;
	domains)     shift; cmd_domains "$@" ;;
	dyndns)      shift; cmd_dyndns "$@" ;;
	generate)    shift; cmd_generate "$@" ;;
	suggest)     shift; cmd_suggest "$@" ;;
	mail-setup)  shift; cmd_mail_setup "$@" ;;
	dkim-add)    shift; cmd_dkim_add "$@" ;;
	acme-dns01)  shift; cmd_acme_dns01 "$@" ;;
	help|--help|-h|'') show_help ;;
	*)           error "Unknown command: $1"; show_help >&2; exit 1 ;;
esac

exit 0
