package exchanger

import (
	"context"
	"errors"
	"net"
	"strings"
	"time"

	"github.com/miekg/dns"
)

type systemExchanger struct {
	rawAddr  string
	timeout  time.Duration
	lookupIP func(ctx context.Context, network, host string) ([]net.IP, error)
}

func newSystemExchanger(rawAddr string, opts Options) Exchanger {
	if opts.timeout <= 0 {
		opts.timeout = 5 * time.Second
	}
	ex := &systemExchanger{
		rawAddr: rawAddr,
		timeout: opts.timeout,
	}
	ex.lookupIP = net.DefaultResolver.LookupIP
	return ex
}

func (ex *systemExchanger) Exchange(ctx context.Context, msg []byte) ([]byte, error) {
	mq := new(dns.Msg)
	if err := mq.Unpack(msg); err != nil {
		return nil, err
	}

	mr := new(dns.Msg)
	mr.SetReply(mq)
	mr.RecursionAvailable = true

	if len(mq.Question) == 0 {
		return mr.Pack()
	}

	q := mq.Question[0]
	network := "ip"
	switch q.Qtype {
	case dns.TypeA:
		network = "ip4"
	case dns.TypeAAAA:
		network = "ip6"
	default:
		return mr.Pack()
	}

	if ex.timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, ex.timeout)
		defer cancel()
	}

	host := strings.TrimSuffix(q.Name, ".")
	ips, err := ex.lookupIP(ctx, network, host)
	if err != nil {
		var dnsErr *net.DNSError
		if !errors.As(err, &dnsErr) || !dnsErr.IsNotFound {
			return nil, err
		}
	}

	for _, ip := range ips {
		switch q.Qtype {
		case dns.TypeA:
			if ip4 := ip.To4(); ip4 != nil {
				mr.Answer = append(mr.Answer, &dns.A{
					Hdr: dns.RR_Header{
						Name:   q.Name,
						Rrtype: dns.TypeA,
						Class:  q.Qclass,
					},
					A: ip4,
				})
			}
		case dns.TypeAAAA:
			if ip.To4() == nil {
				if ip16 := ip.To16(); ip16 != nil {
					mr.Answer = append(mr.Answer, &dns.AAAA{
						Hdr: dns.RR_Header{
							Name:   q.Name,
							Rrtype: dns.TypeAAAA,
							Class:  q.Qclass,
						},
						AAAA: ip16,
					})
				}
			}
		}
	}

	return mr.Pack()
}

func (ex *systemExchanger) String() string {
	if ex.rawAddr == "" {
		return "system"
	}
	return ex.rawAddr
}
