package bunnydns

import (
	"fmt"
	"strings"

	"github.com/StackExchange/dnscontrol/v4/models"
	"github.com/miekg/dns/dnsutil"
	"golang.org/x/exp/slices"
)

var fqdnTypes = []recordType{recordTypeCNAME, recordTypeMX, recordTypeNS, recordTypePTR, recordTypeSRV}

func fromRecordConfig(rc *models.RecordConfig) (*record, error) {
	r := record{
		Type:  recordTypeFromString(rc.Type),
		Name:  rc.GetLabel(),
		Value: rc.GetTargetField(),
		TTL:   rc.TTL,
	}

	switch r.Type {
	case recordTypeNS:
		if r.Name == "" {
			r.TTL = 0
		}
	case recordTypeSRV:
		r.Priority = rc.SrvPriority
		r.Weight = rc.SrvWeight
		r.Port = rc.SrvPort
	case recordTypeCAA:
		r.Flags = rc.CaaFlag
		r.Tag = rc.CaaTag
	case recordTypeMX:
		r.Priority = rc.MxPreference
	}

	// While Bunny DNS does not use trailing dots, it still accepts and even preserves them for certain record types.
	// To avoid confusion, any trailing dots are removed from the record value, except when managing a NullMX record.
	isNullMX := r.Type == recordTypeMX && r.Priority == 0 && r.Value == "."
	if slices.Contains(fqdnTypes, r.Type) && strings.HasSuffix(r.Value, ".") && !isNullMX {
		r.Value = strings.TrimSuffix(r.Value, ".")
	}

	return &r, nil
}

func toRecordConfig(domain string, r *record) (*models.RecordConfig, error) {
	rc := models.RecordConfig{
		Type:     recordTypeToString(r.Type),
		TTL:      r.TTL,
		Original: r,
	}
	rc.SetLabel(r.Name, domain)

	// Bunny DNS always operates with fully-qualified names and does not use any trailing dots.
	// If a record already contains a trailing dot, which the provider UI also accepts, the record value is left as-is.
	recordValue := r.Value
	if slices.Contains(fqdnTypes, r.Type) && !strings.HasSuffix(r.Value, ".") {
		recordValue = dnsutil.AddOrigin(r.Value+".", domain)
	}

	var err error
	switch rc.Type {
	case "BUNNY_DNS_RDR":
		err = rc.SetTarget(r.Value)
	case "CAA":
		err = rc.SetTargetCAA(r.Flags, r.Tag, recordValue)
	case "MX":
		err = rc.SetTargetMX(r.Priority, recordValue)
	case "SRV":
		err = rc.SetTargetSRV(r.Priority, r.Weight, r.Port, recordValue)
	default:
		err = rc.PopulateFromStringFunc(rc.Type, recordValue, domain, nil)
	}
	if err != nil {
		return nil, err
	}

	return &rc, nil
}

type recordType int

const (
	recordTypeA        recordType = 0
	recordTypeAAAA     recordType = 1
	recordTypeCNAME    recordType = 2
	recordTypeTXT      recordType = 3
	recordTypeMX       recordType = 4
	recordTypeRedirect recordType = 5
	recordTypeFlatten  recordType = 6
	recordTypePullZone recordType = 7
	recordTypeSRV      recordType = 8
	recordTypeCAA      recordType = 9
	recordTypePTR      recordType = 10
	recordTypeScript   recordType = 11
	recordTypeNS       recordType = 12
)

func recordTypeFromString(t string) recordType {
	switch t {
	case "A":
		return recordTypeA
	case "AAAA":
		return recordTypeAAAA
	case "CNAME":
		return recordTypeCNAME
	case "TXT":
		return recordTypeTXT
	case "MX":
		return recordTypeMX
	case "FLATTEN":
		return recordTypeFlatten
	case "PULL_ZONE":
		return recordTypePullZone
	case "SRV":
		return recordTypeSRV
	case "CAA":
		return recordTypeCAA
	case "PTR":
		return recordTypePTR
	case "SCRIPT":
		return recordTypeScript
	case "NS":
		return recordTypeNS
	case "BUNNY_DNS_RDR":
		return recordTypeRedirect
	default:
		panic(fmt.Errorf("BUNNY_DNS: rtype %v unimplemented", t))
	}
}

func recordTypeToString(t recordType) string {
	switch t {
	case recordTypeA:
		return "A"
	case recordTypeAAAA:
		return "AAAA"
	case recordTypeCNAME:
		return "CNAME"
	case recordTypeTXT:
		return "TXT"
	case recordTypeMX:
		return "MX"
	case recordTypeRedirect:
		return "BUNNY_DNS_RDR"
	case recordTypeFlatten:
		return "FLATTEN"
	case recordTypePullZone:
		return "PULL_ZONE"
	case recordTypeSRV:
		return "SRV"
	case recordTypeCAA:
		return "CAA"
	case recordTypePTR:
		return "PTR"
	case recordTypeScript:
		return "SCRIPT"
	case recordTypeNS:
		return "NS"
	default:
		panic(fmt.Errorf("BUNNY_DNS: native rtype %v unimplemented", t))
	}
}
