//go:build integration && perftest
// +build integration,perftest

package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/signal"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
)

var config Config

func init() {
	config.SetupFlags("", flag.CommandLine)
}

func main() {
	if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
		flag.CommandLine.PrintDefaults()
		exitErrorf(err, "failed to parse CLI commands")
	}
	if err := config.Validate(); err != nil {
		flag.CommandLine.PrintDefaults()
		exitErrorf(err, "invalid arguments")
	}

	client := NewClient(config.Client)

	var creds *credentials.Credentials
	if config.SDK.Anonymous {
		creds = credentials.AnonymousCredentials
	}

	var endpoint *string
	if v := config.Endpoint; len(v) != 0 {
		endpoint = &v
	}

	sess, err := session.NewSession(&aws.Config{
		HTTPClient:           client,
		Endpoint:             endpoint,
		Credentials:          creds,
		S3Disable100Continue: aws.Bool(!config.SDK.ExpectContinue),
	})
	if err != nil {
		exitErrorf(err, "failed to load config")
	}

	// Create context cancel for Ctrl+C/Interrupt
	ctx, cancelFn := context.WithCancel(context.Background())
	defer cancelFn()
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt)
	go func() {
		<-sigCh
		cancelFn()
	}()

	// Use the request duration timeout if specified.
	if config.RequestDuration != 0 {
		var timeoutFn func()
		ctx, timeoutFn = context.WithTimeout(ctx, config.RequestDuration)
		defer timeoutFn()
	}

	logger := NewLogger(os.Stdout)

	// Start making the requests.
	svc := s3.New(sess)
	var reqCount int64
	errCount := 0
	for {
		trace := doRequest(ctx, reqCount, svc, config)
		select {
		case <-ctx.Done():
			return
		default:
		}
		logger.RecordTrace(trace)

		if err := trace.Err(); err != nil {
			fmt.Fprintf(os.Stderr, err.Error())
			errCount++
		} else {
			errCount = 0
		}

		if config.RequestCount > 0 && reqCount == config.RequestCount {
			return
		}

		reqCount++

		// If the first several requests fail, exist, something is broken.
		if errCount == 5 && reqCount == 5 {
			exitErrorf(trace.Err(), "unable to make requests")
		}

		if config.RequestDelay > 0 {
			time.Sleep(config.RequestDelay)
		}
	}
}

func doRequest(ctx context.Context, id int64, svc *s3.S3, config Config) *RequestTrace {
	traceCtx := NewRequestTrace(ctx, id)
	defer traceCtx.RequestDone()

	resp, err := svc.GetObjectWithContext(traceCtx, &s3.GetObjectInput{
		Bucket: &config.Bucket,
		Key:    &config.Key,
	}, func(r *request.Request) {
		r.Handlers.Send.PushFront(traceCtx.OnSendAttempt)
		r.Handlers.Complete.PushBack(traceCtx.OnCompleteRequest)
		r.Handlers.CompleteAttempt.PushBack(traceCtx.OnCompleteAttempt)
	})
	if err != nil {
		traceCtx.AppendError(fmt.Errorf("request failed, %v", err))
		return traceCtx
	}
	defer resp.Body.Close()

	if n, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
		traceCtx.AppendError(fmt.Errorf("read request body failed, read %v, %v", n, err))
		return traceCtx
	}

	return traceCtx
}

func exitErrorf(err error, msg string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "FAILED: %v\n"+msg+"\n", append([]interface{}{err}, args...)...)
	os.Exit(1)
}
