package transport

import (
	"context"
	"net"
	"sync"
	"time"

	"github.com/ameshkov/dnscrypt/v2"
	mDNS "github.com/miekg/dns"
	"github.com/sagernet/sing-box/adapter"
	C "github.com/sagernet/sing-box/constant"
	"github.com/sagernet/sing-box/dns"
	"github.com/sagernet/sing-box/log"
	"github.com/sagernet/sing-box/option"
	M "github.com/sagernet/sing/common/metadata"
	N "github.com/sagernet/sing/common/network"
)

var _ adapter.DNSTransport = (*SDNSTransport)(nil)

func RegisterSDNS(registry *dns.TransportRegistry) {
	dns.RegisterTransport[option.SDNSDNSServerOptions](registry, C.DNSTypeSDNS, NewSDNSTransport)
}

type SDNSTransport struct {
	dns.TransportAdapter
	client *dnscrypt.Client
	name   string
	stamp  string

	mtx sync.Mutex
}

func NewSDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.SDNSDNSServerOptions) (adapter.DNSTransport, error) {
	transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions)
	if err != nil {
		return nil, err
	}
	return &SDNSTransport{
		client: &dnscrypt.Client{
			Net:     "udp",
			Timeout: 10 * time.Second,
			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
				return transportDialer.DialContext(ctx, N.NetworkName(network), M.ParseSocksaddr(addr))
			},
		},
		stamp: options.Stamp,
	}, err
}

func (t *SDNSTransport) Name() string {
	return t.name
}

func (t *SDNSTransport) Start(adapter.StartStage) error {
	return nil
}

func (t *SDNSTransport) Close() error {
	return nil
}

func (t *SDNSTransport) Reset() {
}

func (t *SDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
	resolverInfo, err := t.client.Dial(t.stamp)
	if err != nil {
		return nil, err
	}
	return t.client.Exchange(message, resolverInfo)
}
