package fortigate

import (
	"fmt"
	"golang.org/x/net/idna"
	"net"
	"strings"

	"github.com/StackExchange/dnscontrol/v4/models"
)

// nativeToRecord – convert an fgDNSRecord coming from FortiGate into a *models.RecordConfig that dnscontrol understands
func nativeToRecord(domain string, n fgDNSRecord) (*models.RecordConfig, error) {
	rc := &models.RecordConfig{}
	rc.Type = strings.ToUpper(n.Type)
	rc.Original = n

	// Label / Name
	label := strings.TrimSuffix(n.Hostname, ".")
	if label == "@" {
		label = ""
	}
	rc.SetLabel(label, domain)

	// TTL
	if n.TTL == 0 {
		rc.TTL = 0 // inherit
	} else {
		rc.TTL = n.TTL
	}

	// Status → Metadata
	if strings.ToLower(n.Status) != "enable" {
		if rc.Metadata == nil {
			rc.Metadata = map[string]string{}
		}
		rc.Metadata["fortigate_status"] = "disable"
	}

	// Type-specific fields
	switch rc.Type {
	case "A":
		ip := net.ParseIP(n.IP)
		if ip == nil || ip.To4() == nil {
			return nil, fmt.Errorf("[FORTIGATE] Invalid IPv4 address %q in %+v", n.IP, n)
		}
		rc.SetTargetIP(ip)

	case "AAAA":
		ip := net.ParseIP(n.IPv6)
		if ip == nil || ip.To16() == nil || ip.To4() != nil {
			return nil, fmt.Errorf("[FORTIGATE] Invalid IPv6 address %q in %+v", n.IPv6, n)
		}
		rc.SetTargetIP(ip)

	case "CNAME":
		if n.CanonicalName == "" {
			return nil, fmt.Errorf("[FORTIGATE] CNAME record without canonical-name (id=%d)", n.ID)
		}
		if err := rc.SetTarget(n.CanonicalName); err != nil {
			return nil, err
		}

	case "NS":
		if n.Hostname == "" {
			return nil, fmt.Errorf("[FORTIGATE] NS record missing hostname (id=%d)", n.ID)
		}

		rc.SetLabel("@", domain)
		if err := rc.SetTarget(n.Hostname); err != nil {
			return nil, err
		}

	case "MX":
		if n.Hostname == "" {
			return nil, fmt.Errorf("[FORTIGATE] MX record missing hostname (id=%d)", n.ID)
		}

		rc.SetLabel("@", domain)
		rc.MxPreference = n.Preference

		if err := rc.SetTarget(n.Hostname); err != nil {
			return nil, err
		}

	default:
		// Not supported due to FortiGate limitations
		return nil, fmt.Errorf("[FORTIGATE] Record type %q is not supported by fortigate provider", rc.Type)
	}

	return rc, nil
}

func recordsToNative(recs models.Records) ([]*fgDNSRecord, []error) {
	var resourceRecords []*fgDNSRecord
	var errors []error

	id := 1

	for _, record := range recs {

		n := &fgDNSRecord{
			Status: "enable",
			Type:   strings.ToUpper(record.Type),
		}

		// TTL
		if ttl := record.TTL; ttl > 0 {
			n.TTL = ttl
		}

		// Wildcard support
		if strings.Contains(record.GetLabelFQDN(), "*") {
			errors = append(errors, fmt.Errorf("[FORTIGATE] Wildcard records are not supported: %s", record.GetLabelFQDN()))
			continue
		}

		// Status from Metadata
		if v, ok := record.Metadata["fortigate_status"]; ok && strings.ToLower(v) == "disable" {
			n.Status = "disable"
		}

		// Hostname (Label)
		label := record.GetLabel()
		if label == "" {
			label = "@"
		}
		n.Hostname = label

		// Type-specific fields
		switch n.Type {
		case "A":
			ip := record.GetTargetIP()
			if ip == nil || ip.To4() == nil {
				errors = append(errors, fmt.Errorf("[FORTIGATE] A record is missing a valid IPv4 address: %s", record.GetLabelFQDN()))
				continue
			}
			n.IP = ip.String()

		case "AAAA":
			ip := record.GetTargetIP()
			if ip == nil || ip.To16() == nil || ip.To4() != nil {
				errors = append(errors, fmt.Errorf("[FORTIGATE] AAAA record is missing a valid IPv6 address: %s", record.GetLabelFQDN()))
				continue
			}
			n.IPv6 = ip.String()

		case "CNAME":
			target := record.GetTargetField()
			if ascii, err := idna.ToASCII(target); err == nil {
				target = ascii
			}
			n.CanonicalName = target

		case "NS":
			target := record.GetTargetField()
			if ascii, err := idna.ToASCII(target); err == nil {
				target = ascii
			}
			n.Hostname = target
			n.CanonicalName = ""

		case "MX":
			target := record.GetTargetField()
			if ascii, err := idna.ToASCII(target); err == nil {
				target = ascii
			}
			n.Hostname = target
			n.Preference = record.MxPreference
			n.CanonicalName = ""

		default:
			errors = append(errors, fmt.Errorf("[FORTIGATE] Record type %q is not supported: %s", n.Type, record.GetLabelFQDN()))
			continue
		}

		n.ID = id
		id++

		resourceRecords = append(resourceRecords, n)
	}

	return resourceRecords, errors
}
