package twitchhttp

import (
	"fmt"
	"math/rand"
	"net"
	"regexp"
	"strings"
	"time"

	"github.com/cactus/go-statsd-client/statsd"
)

func newDialer(stats statsd.Statter, statPrefix string) *dialer {
	// Make sure that DNS stats are namespaced properly by ensuring
	// the last character is '.'.
	if statPrefix == "" {
		statPrefix = "dns."
	}
	if !strings.HasSuffix(statPrefix, ".") {
		statPrefix += "."
	}

	return &dialer{
		dial: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).Dial,
		stats:      stats,
		statPrefix: statPrefix,
		lookupIP:   net.LookupIP,
	}
}

type dialer struct {
	dial       func(network string, addr string) (net.Conn, error)
	stats      statsd.Statter
	statPrefix string
	lookupIP   func(host string) ([]net.IP, error)
}

func (d *dialer) Dial(network string, addr string) (net.Conn, error) {
	// If we aren't tracking DNS stats, simply use the default dial
	if d.stats == nil {
		return d.dial(network, addr)
	}

	// TCP connections should always have both host and port
	host, port, err := net.SplitHostPort(addr)
	if err != nil {
		return nil, err
	}
	// If addr host is an IP, we don't need to do a DNS lookup
	if net.ParseIP(host) != nil {
		return d.dial(network, addr)
	}

	ip, duration, err := d.randomIP(network, host)
	d.stats.TimingDuration(d.stat(host, err), duration, 1.0)
	if err != nil {
		return nil, err
	}

	return d.dial(network, net.JoinHostPort(ip.String(), port))
}

var invalidStatRegexp = regexp.MustCompile(`[^A-Za-z0-9-]+`)

func (d *dialer) stat(host string, err error) string {
	statHost := invalidStatRegexp.ReplaceAllString(host, "_")
	result := "success"
	if err != nil {
		result = "failure"
	}
	return fmt.Sprintf("%s%s.%s", d.statPrefix, statHost, result)
}

func (d *dialer) randomIP(network string, host string) (net.IP, time.Duration, error) {
	start := time.Now()
	ips, err := d.lookupIP(host)
	duration := time.Now().Sub(start)
	if err != nil {
		return nil, duration, err
	}

	// filter out any IPs that aren't the correct version, only TCP is allowed
	var allowed func(net.IP) bool
	switch network {
	case "tcp4":
		allowed = func(ip net.IP) bool { return ip.To4() != nil }
	case "tcp6":
		allowed = func(ip net.IP) bool { return ip.To16() != nil }
	default:
		allowed = func(ip net.IP) bool { return true }
	}

	var filtered []net.IP
	for _, ip := range ips {
		if allowed(ip) {
			filtered = append(filtered, ip)
		}
	}

	if len(filtered) == 0 {
		if len(filtered) == len(ips) {
			return nil, duration, fmt.Errorf("twitchhttp: dns lookup had 0 results for %s", host)
		}
		return nil, duration, fmt.Errorf("twitchhttp: dns lookup had 0 %s results for %s", network, host)
	}

	return filtered[rand.Intn(len(filtered))], duration, nil
}
