package manager

import (
	"context"
	"time"

	"github.com/cenkalti/backoff/v4"

	"github.com/gotd/td/clock"
	"github.com/gotd/td/internal/mtproto"
	"github.com/gotd/td/internal/tdsync"
	"github.com/gotd/td/tg"
)

// SetupCallback is an optional setup connection callback.
type SetupCallback = func(ctx context.Context, invoker tg.Invoker) error

// ConnOptions is a Telegram client connection options.
type ConnOptions struct {
	DC      int
	Test    bool
	Device  DeviceConfig
	Handler Handler
	Setup   SetupCallback
	Backoff func(ctx context.Context) backoff.BackOff
}

func defaultBackoff(c clock.Clock) func(ctx context.Context) backoff.BackOff {
	return func(ctx context.Context) backoff.BackOff {
		b := backoff.NewExponentialBackOff()
		b.Clock = c
		b.MaxElapsedTime = time.Second * 30
		b.MaxInterval = time.Second * 5
		return backoff.WithContext(b, ctx)
	}
}

// setDefaults sets default values.
func (c *ConnOptions) setDefaults(connClock clock.Clock) {
	if c.DC == 0 {
		c.DC = 2
	}
	// It's okay to use zero value Test.
	c.Device.SetDefaults()
	if c.Handler == nil {
		c.Handler = NoopHandler{}
	}
	if c.Backoff == nil {
		c.Backoff = defaultBackoff(connClock)
	}
}

// CreateConn creates new connection.
func CreateConn(
	create mtproto.Dialer,
	mode ConnMode,
	appID int,
	opts mtproto.Options,
	connOpts ConnOptions,
) *Conn {
	connOpts.setDefaults(opts.Clock)
	conn := &Conn{
		mode:        mode,
		appID:       appID,
		device:      connOpts.Device,
		clock:       opts.Clock,
		handler:     connOpts.Handler,
		sessionInit: tdsync.NewReady(),
		gotConfig:   tdsync.NewReady(),
		dead:        tdsync.NewReady(),
		setup:       connOpts.Setup,
		connBackoff: connOpts.Backoff,
	}

	conn.log = opts.Logger
	opts.DC = connOpts.DC
	if connOpts.Test {
		// New key exchange algorithm requires DC ID and uses mapping like MTProxy.
		// +10000 for test DC, *-1 for media-only.
		opts.DC += 10000
	}
	opts.Handler = conn
	opts.Logger = conn.log.Named("mtproto")
	conn.proto = mtproto.New(create, opts)

	return conn
}
