package launcher

import (
	"context"
	"math/rand"
	"net/http"
	"strconv"
	"sync"
	"time"

	"go.uber.org/zap"

	"code.justin.tv/websocket-edge/server/internal/logs"
	"code.justin.tv/websocket-edge/server/internal/metrics"
	"code.justin.tv/websocket-edge/server/internal/util"
	"code.justin.tv/websocket-edge/server/loadtest/user"
)

type LaunchConf struct {
	// The duration that the launcher waits between spinning up new loadtest users.
	UserInterval time.Duration
	// How many users the launcher will spin up.
	NumUsers int
	// How often each loadtest user will send a subscription to the websocket edge.
	SubscriptionInterval time.Duration
	// How many messages each user will expect to receive.
	NumMessages int
	// How frequently each user should expect to receive a message.
	MessageInterval time.Duration
	// How long to wait for all of the users to complete execution before cancelling everything and returning.
	SuiteTimeout time.Duration
}

func jitteredTicker(d time.Duration, jitter time.Duration) <-chan bool {
	c := make(chan bool, 1)
	dMilli := int(d.Seconds() * 1000)
	go func() {
		for {
			jit := rand.Intn(int(jitter.Seconds() * 1000))
			nextDelta := time.Duration(dMilli+jit) * time.Millisecond
			after := time.After(nextDelta)
			<-after
			c <- true
		}
	}()
	return c
}

func Start(ctx context.Context, logger logs.Logger, statter metrics.Statter, conf *LaunchConf) {
	t := jitteredTicker(conf.UserInterval, 40*time.Millisecond)
	wg := &sync.WaitGroup{}
	ctx, cancel := context.WithCancel(ctx)

	// 20 hosts= 4k idle connections, 10k total connections
	tr := &http.Transport{
		MaxIdleConnsPerHost: 1000,
		IdleConnTimeout:     60 * time.Second,
		MaxConnsPerHost:     2000,
	}
	httpClient := &http.Client{
		Transport: tr,
	}

	userTimeout := conf.MessageInterval*time.Duration(conf.NumMessages) + 60*time.Second

	for i := 0; i < conf.NumUsers; i++ {
		wg.Add(1)
		user := user.NewBasicUser(logger, statter, wg, httpClient, strconv.Itoa(i))
		go user.Simulate(ctx, conf.NumMessages, userTimeout, conf.SubscriptionInterval)
		<-t
	}

	timedOut := util.WaitTimeout(wg, conf.SuiteTimeout)
	cancel()
	logger.Info("Finished load test", zap.Bool("timedOut", timedOut))
}
