package dnsforward

import (
	"context"
	"crypto/tls"
	"fmt"
	"net"
	"net/url"
	"strings"
	"sync"

	"github.com/joomcode/errorx"
)

type bootstrapper struct {
	address        string        // in form of "tls://one.one.one.one:853"
	resolver       *net.Resolver // resolver to use to resolve hostname, if neccessary
	resolved       string        // in form "IP:port"
	resolvedConfig *tls.Config
	sync.Mutex
}

func toBoot(address, bootstrapAddr string) bootstrapper {
	var resolver *net.Resolver
	if bootstrapAddr != "" {
		resolver = &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				d := net.Dialer{}
				return d.DialContext(ctx, network, bootstrapAddr)
			},
		}
	}
	return bootstrapper{
		address:  address,
		resolver: resolver,
	}
}

// will get usable IP address from Address field, and caches the result
func (n *bootstrapper) get() (string, *tls.Config, error) {
	// TODO: RLock() here but atomically upgrade to Lock() if fast path doesn't work
	n.Lock()
	if n.resolved != "" { // fast path
		retval, tlsconfig := n.resolved, n.resolvedConfig
		n.Unlock()
		return retval, tlsconfig, nil
	}

	//
	// slow path
	//

	defer n.Unlock()

	justHostPort := n.address
	if strings.Contains(n.address, "://") {
		url, err := url.Parse(n.address)
		if err != nil {
			return "", nil, errorx.Decorate(err, "Failed to parse %s", n.address)
		}

		justHostPort = url.Host
	}

	// convert host to IP if neccessary, we know that it's scheme://hostname:port/

	// get a host without port
	host, port, err := net.SplitHostPort(justHostPort)
	if err != nil {
		return "", nil, fmt.Errorf("bootstrapper requires port in address %s", n.address)
	}

	// if it's an IP
	ip := net.ParseIP(host)
	if ip != nil {
		n.resolved = justHostPort
		return n.resolved, nil, nil
	}

	//
	// if it's a hostname
	//

	resolver := n.resolver // no need to check for nil resolver -- documented that nil is default resolver
	addrs, err := resolver.LookupIPAddr(context.TODO(), host)
	if err != nil {
		return "", nil, errorx.Decorate(err, "Failed to lookup %s", host)
	}
	for _, addr := range addrs {
		// TODO: support ipv6, support multiple ipv4
		if addr.IP.To4() == nil {
			continue
		}
		ip = addr.IP
		break
	}

	if ip == nil {
		// couldn't find any suitable IP address
		return "", nil, fmt.Errorf("Couldn't find any suitable IP address for host %s", host)
	}

	n.resolved = net.JoinHostPort(ip.String(), port)
	n.resolvedConfig = &tls.Config{ServerName: host}
	return n.resolved, n.resolvedConfig, nil
}