package dcs

import (
	"context"
	"io"

	"github.com/go-faster/errors"
	"nhooyr.io/websocket"

	"github.com/gotd/td/internal/crypto"
	"github.com/gotd/td/internal/mtproxy"
	"github.com/gotd/td/internal/mtproxy/obfuscator"
	"github.com/gotd/td/internal/proto/codec"
	"github.com/gotd/td/internal/wsutil"
	"github.com/gotd/td/transport"
)

var _ Resolver = ws{}

type ws struct {
	dialOptions *websocket.DialOptions
	protocol    protocol

	tag  [4]byte
	rand io.Reader
}

func (w ws) connect(ctx context.Context, dc int, domains map[int]string) (transport.Conn, error) {
	addr, ok := domains[dc]
	if !ok {
		return nil, errors.Errorf("domain for %d not found", dc)
	}

	conn, resp, err := websocket.Dial(ctx, addr, w.dialOptions)
	if resp != nil && resp.Body != nil {
		_ = resp.Body.Close()
	}
	if err != nil {
		return nil, errors.Wrap(err, "dial ws")
	}
	obsConn := obfuscator.Obfuscated2(w.rand, wsutil.NetConn(conn))

	if err := obsConn.Handshake(w.tag, dc, mtproxy.Secret{
		Secret: nil,
		Type:   mtproxy.Simple,
	}); err != nil {
		return nil, errors.Wrap(err, "handshake")
	}

	transportConn, err := w.protocol.Handshake(obsConn)
	if err != nil {
		return nil, errors.Wrap(err, "transport handshake")
	}

	return transportConn, nil
}

func (w ws) Primary(ctx context.Context, dc int, list List) (transport.Conn, error) {
	return w.connect(ctx, dc, list.Domains)
}

func (w ws) MediaOnly(ctx context.Context, dc int, list List) (transport.Conn, error) {
	return nil, errors.Errorf("can't resolve %d: MediaOnly is unsupported", dc)
}

func (w ws) CDN(ctx context.Context, dc int, list List) (transport.Conn, error) {
	return nil, errors.Errorf("can't resolve %d: CDN is unsupported", dc)
}

// WebsocketOptions is Websocket resolver creation options.
type WebsocketOptions struct {
	// Dialer specifies the websocket dialer.
	// If Dialer is nil, then the resolver dials using websocket.DefaultDialer.
	DialOptions *websocket.DialOptions
	// Random source for MTProxy obfuscator.
	Rand io.Reader
}

func (m *WebsocketOptions) setDefaults() {
	if m.DialOptions == nil {
		m.DialOptions = &websocket.DialOptions{Subprotocols: []string{
			"binary",
		}}
	}
	if m.Rand == nil {
		m.Rand = crypto.DefaultRand()
	}
}

// Websocket creates Websocket DC resolver.
//
// See https://core.telegram.org/mtproto/transports#websocket.
func Websocket(opts WebsocketOptions) Resolver {
	cdc := codec.Intermediate{}
	opts.setDefaults()

	return ws{
		dialOptions: opts.DialOptions,
		protocol:    transport.NewProtocol(func() transport.Codec { return codec.NoHeader{Codec: cdc} }),
		tag:         cdc.ObfuscatedTag(),
		rand:        opts.Rand,
	}
}
