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

package main

import (
	"context"
	"crypto/tls"
	"net/http/httptrace"
	"time"

	"github.com/aws/aws-sdk-go/aws/request"
)

type RequestTrace struct {
	ID int64
	context.Context

	start, finish time.Time

	errs     Errors
	attempts []RequestAttempt

	curAttempt RequestAttempt
}

func NewRequestTrace(ctx context.Context, id int64) *RequestTrace {
	rt := &RequestTrace{
		ID:       id,
		start:    time.Now(),
		attempts: []RequestAttempt{},
	}

	trace := &httptrace.ClientTrace{
		GetConn:              rt.getConn,
		GotConn:              rt.gotConn,
		PutIdleConn:          rt.putIdleConn,
		GotFirstResponseByte: rt.gotFirstResponseByte,
		Got100Continue:       rt.got100Continue,
		DNSStart:             rt.dnsStart,
		DNSDone:              rt.dnsDone,
		ConnectStart:         rt.connectStart,
		ConnectDone:          rt.connectDone,
		TLSHandshakeStart:    rt.tlsHandshakeStart,
		TLSHandshakeDone:     rt.tlsHandshakeDone,
		WroteHeaders:         rt.wroteHeaders,
		Wait100Continue:      rt.wait100Continue,
		WroteRequest:         rt.wroteRequest,
	}

	rt.Context = httptrace.WithClientTrace(ctx, trace)

	return rt
}

func (rt *RequestTrace) AppendError(err error) {
	rt.errs = append(rt.errs, err)
}
func (rt *RequestTrace) OnCompleteAttempt(r *request.Request) {
	rt.curAttempt.Start = r.AttemptTime
	rt.curAttempt.Finish = time.Now()
	rt.curAttempt.Err = r.Error

	rt.attempts = append(rt.attempts, rt.curAttempt)
	rt.curAttempt = RequestAttempt{}
}
func (rt *RequestTrace) OnSendAttempt(r *request.Request) {
	rt.curAttempt.SendStart = time.Now()
}
func (rt *RequestTrace) OnCompleteRequest(r *request.Request) {}
func (rt *RequestTrace) RequestDone() {
	rt.finish = time.Now()
	// Last attempt includes reading the response body
	rt.attempts[len(rt.attempts)-1].Finish = rt.finish
}

func (rt *RequestTrace) Err() error {
	return rt.errs
}
func (rt *RequestTrace) TotalLatency() time.Duration {
	return rt.finish.Sub(rt.start)
}
func (rt *RequestTrace) Attempts() []RequestAttempt {
	return rt.attempts
}
func (rt *RequestTrace) Retries() int {
	return len(rt.attempts) - 1
}

func (rt *RequestTrace) getConn(hostPort string) {}
func (rt *RequestTrace) gotConn(info httptrace.GotConnInfo) {
	rt.curAttempt.Reused = info.Reused
}
func (rt *RequestTrace) putIdleConn(err error) {}
func (rt *RequestTrace) gotFirstResponseByte() {
	rt.curAttempt.FirstResponseByte = time.Now()
}
func (rt *RequestTrace) got100Continue() {}
func (rt *RequestTrace) dnsStart(info httptrace.DNSStartInfo) {
	rt.curAttempt.DNSStart = time.Now()
}
func (rt *RequestTrace) dnsDone(info httptrace.DNSDoneInfo) {
	rt.curAttempt.DNSDone = time.Now()
}
func (rt *RequestTrace) connectStart(network, addr string) {
	rt.curAttempt.ConnectStart = time.Now()
}
func (rt *RequestTrace) connectDone(network, addr string, err error) {
	rt.curAttempt.ConnectDone = time.Now()
}
func (rt *RequestTrace) tlsHandshakeStart() {
	rt.curAttempt.TLSHandshakeStart = time.Now()
}
func (rt *RequestTrace) tlsHandshakeDone(state tls.ConnectionState, err error) {
	rt.curAttempt.TLSHandshakeDone = time.Now()
}
func (rt *RequestTrace) wroteHeaders()    {}
func (rt *RequestTrace) wait100Continue() {}
func (rt *RequestTrace) wroteRequest(info httptrace.WroteRequestInfo) {
	rt.curAttempt.RequestWritten = time.Now()
}

type RequestAttempt struct {
	Start, Finish time.Time
	SendStart     time.Time
	Err           error

	Reused bool

	DNSStart, DNSDone                   time.Time
	ConnectStart, ConnectDone           time.Time
	TLSHandshakeStart, TLSHandshakeDone time.Time
	RequestWritten                      time.Time
	FirstResponseByte                   time.Time
}
