package config

import (
	"fmt"
	"math/rand"
	"net"
	"strconv"
	"strings"

	"gopkg.in/jcmturner/dnsutils.v1"
)

// GetKDCs returns the count of KDCs available and a map of KDC host names keyed on preference order.
func (c *Config) GetKDCs(realm string, tcp bool) (int, map[int]string, error) {
	if realm == "" {
		realm = c.LibDefaults.DefaultRealm
	}
	kdcs := make(map[int]string)
	var count int

	// Get the KDCs from the krb5.conf.
	var ks []string
	for _, r := range c.Realms {
		if r.Realm != realm {
			continue
		}
		ks = r.KDC
	}
	count = len(ks)

	if count > 0 {
		// Order the kdcs randomly for preference.
		kdcs = randServOrder(ks)
		return count, kdcs, nil
	}

	if !c.LibDefaults.DNSLookupKDC {
		return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm)
	}

	// Use DNS to resolve kerberos SRV records.
	proto := "udp"
	if tcp {
		proto = "tcp"
	}
	index, addrs, err := dnsutils.OrderedSRV("kerberos", proto, realm)
	if err != nil {
		return count, kdcs, err
	}
	if len(addrs) < 1 {
		return count, kdcs, fmt.Errorf("no KDC SRV records found for realm %s", realm)
	}
	count = index
	for k, v := range addrs {
		kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
	}
	return count, kdcs, nil
}

// GetKpasswdServers returns the count of kpasswd servers available and a map of kpasswd host names keyed on preference order.
// https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms - see kpasswd_server section
func (c *Config) GetKpasswdServers(realm string, tcp bool) (int, map[int]string, error) {
	kdcs := make(map[int]string)
	var count int

	// Use DNS to resolve kerberos SRV records if configured to do so in krb5.conf.
	if c.LibDefaults.DNSLookupKDC {
		proto := "udp"
		if tcp {
			proto = "tcp"
		}
		c, addrs, err := dnsutils.OrderedSRV("kpasswd", proto, realm)
		if err != nil {
			return count, kdcs, err
		}
		if c < 1 {
			c, addrs, err = dnsutils.OrderedSRV("kerberos-adm", proto, realm)
			if err != nil {
				return count, kdcs, err
			}
		}
		if len(addrs) < 1 {
			return count, kdcs, fmt.Errorf("no kpasswd or kadmin SRV records found for realm %s", realm)
		}
		count = c
		for k, v := range addrs {
			kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
		}
	} else {
		// Get the KDCs from the krb5.conf an order them randomly for preference.
		var ks []string
		var ka []string
		for _, r := range c.Realms {
			if r.Realm == realm {
				ks = r.KPasswdServer
				ka = r.AdminServer
				break
			}
		}
		if len(ks) < 1 {
			for _, k := range ka {
				h, _, err := net.SplitHostPort(k)
				if err != nil {
					continue
				}
				ks = append(ks, h+":464")
			}
		}
		count = len(ks)
		if count < 1 {
			return count, kdcs, fmt.Errorf("no kpasswd or kadmin defined in configuration for realm %s", realm)
		}
		kdcs = randServOrder(ks)
	}
	return count, kdcs, nil
}

func randServOrder(ks []string) map[int]string {
	kdcs := make(map[int]string)
	count := len(ks)
	i := 1
	if count > 1 {
		l := len(ks)
		for l > 0 {
			ri := rand.Intn(l)
			kdcs[i] = ks[ri]
			if l > 1 {
				// Remove the entry from the source slice by swapping with the last entry and truncating
				ks[len(ks)-1], ks[ri] = ks[ri], ks[len(ks)-1]
				ks = ks[:len(ks)-1]
				l = len(ks)
			} else {
				l = 0
			}
			i++
		}
	} else {
		kdcs[i] = ks[0]
	}
	return kdcs
}
