package netutil

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"sync"
	"time"
)

var dnsCache sync.Map

func init() {
	go func() {
		for {
			time.Sleep(5 * time.Minute)
			dnsCache.Range(func(key, _ any) bool {
				dnsCache.Delete(key)
				return true
			})
		}
	}()
}

func newTransport(dialTimeout, tlsTimeout, headerTimeout time.Duration) *http.Transport {
	return &http.Transport{
		MaxIdleConns:          100,
		MaxIdleConnsPerHost:   10,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   tlsTimeout,
		ResponseHeaderTimeout: headerTimeout,
		ExpectContinueTimeout: 1 * time.Second,
		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
			host, port, err := net.SplitHostPort(addr)
			if err != nil {
				return nil, err
			}

			ips, ok := dnsCache.Load(host)
			if !ok {
				resolved, err := net.DefaultResolver.LookupIPAddr(ctx, host)
				if err != nil {
					return nil, fmt.Errorf("proxy dns lookup: %w", err)
				}
				dnsCache.Store(host, resolved)
				ips = resolved
			}

			return dialIPs(ctx, network, host, port, ips.([]net.IPAddr), dialTimeout)
		},
	}
}

func dialIPs(ctx context.Context, network, host, port string, ips []net.IPAddr, timeout time.Duration) (net.Conn, error) {
	var firstErr error
	for _, ip := range ips {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
		}
		dialer := net.Dialer{Timeout: timeout}
		conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
		if err == nil {
			return conn, nil
		}
		if firstErr == nil {
			firstErr = err
		}
	}
	return nil, fmt.Errorf("proxy dial %s: %w", host, firstErr)
}

func NewClient() *http.Client {
	return &http.Client{
		Transport: newTransport(10*time.Second, 10*time.Second, 30*time.Second),
		Timeout:   60 * time.Second,
	}
}

func NewStreamingClient() *http.Client {
	return &http.Client{
		Transport: newTransport(10*time.Second, 10*time.Second, 15*time.Second),
	}
}
