package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"net/http"
	"encoding/json"
	"strings"
)


const API_DOMAIN string = "https://api.porkbun.com"
const ENDPOINT_GET string = "/api/json/v3/dns/retrieve/"
const ENDPOINT_SET string = "/api/json/v3/dns/edit/"

// error helper
func checkE(e error) {
	if e != nil {
		log.Fatal(e)
		panic(e)
	}
}

// http request helper
func createBody(bodyMap map[string]string) io.Reader {
  bodyData, err := json.Marshal(bodyMap)
  checkE(err)
	body := string(bodyData)
  return strings.NewReader(body)
}

// http response helper
func readBody(res *http.Response) []byte {
	body, err := io.ReadAll(res.Body)
	res.Body.Close()
	if res.StatusCode > 299 {
		log.Fatalf("Response failed with status code: %d and\nbody: %s\n", res.StatusCode, body)
	}
	checkE(err) 
	return body
}

func syncDns(domain string, apiSecret string, apiKey string, serverIp string) bool {
  // construct endpoint url
	url := API_DOMAIN + ENDPOINT_GET + domain

	// use api keys to construct request body for porkbun
	body := createBody(map[string]string{
		"secretapikey" : apiSecret,
		"apikey" : apiKey,
	})
	
  // submit request for domain's DNS records
	res, err := http.Post(url, "application/json", body)
	checkE(err)

	// parse records out from response body
	var bodyJson map[string]interface{}
	err = json.Unmarshal(readBody(res), &bodyJson)
	checkE(err)
	dnsRecords := bodyJson["records"].([]interface{})

	// identify outdated A records 
	aRecordsToUpdate := make(map[string]string)
	for i := range len(dnsRecords) {
		record := dnsRecords[i].(map[string]interface{})
		if record["type"].(string) != "A" { continue }
		if record["content"].(string) == serverIp { continue }
		aRecordsToUpdate[record["name"].(string)] = record["id"].(string)
	}

	// we are potentially done now
	if len(aRecordsToUpdate) == 0 {
		fmt.Println(domain + " is up to date")
		return false
	} 

	// update each outdated A record 
	for recordName, recordId := range aRecordsToUpdate {
		url = API_DOMAIN + ENDPOINT_SET + domain + "/" + recordId
		subdomain := strings.Split(recordName, ".")[0]
		body = createBody(map[string]string{
			"secretapikey" : apiSecret,
			"apikey" : apiKey,
			"content" : serverIp,
			"name" : subdomain,
			"type": "A",
			"ttl": "600",
		})

		// make request to update A record
		res, err = http.Post(url, "application/json", body)
		checkE(err)
		fmt.Println("UPDATED " + recordName)
	}

	return true
}

func main() {
	// open + deserialize env JSON 
	envJson, err := os.ReadFile(".env")
	checkE(err)
	var env map[string]interface{}
	err = json.Unmarshal(envJson, &env)
	checkE(err)

	// parse/organize api keys + domains to update
	apiSecret := env["secretapikey"].(string)
	apiKey 		:= env["apikey"].(string)
	domains		:= env["domains"].([]interface{})

	// determine server's current ip
 	res, err := http.Get("https://api.ipify.org")
	checkE(err)
	ip := string(readBody(res))

	fmt.Println("SERVER IP: " + ip)

	// loop through all domains + update DNS records
	var domain string
	for i := range len(domains) {
		domain = domains[i].(string)
		fmt.Println("checking " + domain)
		syncDns(domain, apiSecret, apiKey, ip)
	}
}

