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

package main

import (
	"encoding/csv"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"time"
)

type Logger struct {
	out *csv.Writer
}

func NewLogger(writer io.Writer) *Logger {
	l := &Logger{
		out: csv.NewWriter(writer),
	}

	err := l.out.Write([]string{
		"ID", "Attempt",
		"Latency",
		"DNSStart", "DNSDone", "DNSDur",
		"ConnectStart", "ConnectDone", "ConnectDur",
		"TLSStart", "TLSDone", "TLSDur",
		"WriteReq", "RespFirstByte", "WaitRespFirstByte",
		"Error",
	})
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to write header trace, %v\n", err)
	}

	return l
}

func (l *Logger) RecordTrace(trace *RequestTrace) {
	req := RequestReport{
		ID:           trace.ID,
		TotalLatency: durToMSString(trace.TotalLatency()),
		Retries:      trace.Retries(),
	}

	for i, a := range trace.Attempts() {
		attempt := AttemptReport{
			Reused:     a.Reused,
			SDKMarshal: durToMSString(a.SendStart.Sub(a.Start)),
			ReqWritten: durToMSString(a.RequestWritten.Sub(a.SendStart)),
			Latency:    durToMSString(a.Finish.Sub(a.Start)),
			Err:        a.Err,
		}

		if !a.FirstResponseByte.IsZero() {
			attempt.RespFirstByte = durToMSString(a.FirstResponseByte.Sub(a.SendStart))
			attempt.WaitRespFirstByte = durToMSString(a.FirstResponseByte.Sub(a.RequestWritten))
		}

		if !a.Reused {
			attempt.DNSStart = durToMSString(a.DNSStart.Sub(a.SendStart))
			attempt.DNSDone = durToMSString(a.DNSDone.Sub(a.SendStart))
			attempt.DNS = durToMSString(a.DNSDone.Sub(a.DNSStart))

			attempt.ConnectStart = durToMSString(a.ConnectStart.Sub(a.SendStart))
			attempt.ConnectDone = durToMSString(a.ConnectDone.Sub(a.SendStart))
			attempt.Connect = durToMSString(a.ConnectDone.Sub(a.ConnectStart))

			attempt.TLSHandshakeStart = durToMSString(a.TLSHandshakeStart.Sub(a.SendStart))
			attempt.TLSHandshakeDone = durToMSString(a.TLSHandshakeDone.Sub(a.SendStart))
			attempt.TLSHandshake = durToMSString(a.TLSHandshakeDone.Sub(a.TLSHandshakeStart))
		}

		req.Attempts = append(req.Attempts, attempt)

		var reqErr string
		if attempt.Err != nil {
			reqErr = strings.Replace(attempt.Err.Error(), "\n", `\n`, -1)
		}
		err := l.out.Write([]string{
			strconv.Itoa(int(req.ID)),
			strconv.Itoa(i + 1),
			attempt.Latency,
			attempt.DNSStart, attempt.DNSDone, attempt.DNS,
			attempt.ConnectStart, attempt.ConnectDone, attempt.Connect,
			attempt.TLSHandshakeStart, attempt.TLSHandshakeDone, attempt.TLSHandshake,
			attempt.ReqWritten,
			attempt.RespFirstByte,
			attempt.WaitRespFirstByte,
			reqErr,
		})
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to write request trace, %v\n", err)
		}
		l.out.Flush()
	}
}

func durToMSString(v time.Duration) string {
	ms := float64(v) / float64(time.Millisecond)
	return fmt.Sprintf("%0.6f", ms)
}

type RequestReport struct {
	ID           int64
	TotalLatency string
	Retries      int

	Attempts []AttemptReport
}
type AttemptReport struct {
	Reused bool
	Err    error

	SDKMarshal string `json:",omitempty"`

	DNSStart string `json:",omitempty"`
	DNSDone  string `json:",omitempty"`
	DNS      string `json:",omitempty"`

	ConnectStart string `json:",omitempty"`
	ConnectDone  string `json:",omitempty"`
	Connect      string `json:",omitempty"`

	TLSHandshakeStart string `json:",omitempty"`
	TLSHandshakeDone  string `json:",omitempty"`
	TLSHandshake      string `json:",omitempty"`

	ReqWritten        string `json:",omitempty"`
	RespFirstByte     string `json:",omitempty"`
	WaitRespFirstByte string `json:",omitempty"`
	Latency           string `json:",omitempty"`
}
