package archival

//
// Saves HTTP events
//

import (
	"bytes"
	"io"
	"net/http"
	"time"

	"github.com/ooni/probe-cli/v3/internal/model"
	"github.com/ooni/probe-cli/v3/internal/netxlite"
)

// HTTPRoundTripEvent contains an HTTP round trip.
type HTTPRoundTripEvent struct {
	Failure                 error
	Finished                time.Time
	Method                  string
	RequestHeaders          http.Header
	ResponseBody            []byte
	ResponseBodyIsTruncated bool
	ResponseBodyLength      int64
	ResponseHeaders         http.Header
	Started                 time.Time
	StatusCode              int64
	Transport               string
	URL                     string
}

// HTTPRoundTrip performs the round trip with the given transport and
// the given arguments and saves the results into the saver.
//
// The maxBodySnapshotSize argument controls the maximum size of the
// body snapshot that we collect along with the HTTP round trip.
func (s *Saver) HTTPRoundTrip(
	txp model.HTTPTransport, maxBodySnapshotSize int64,
	req *http.Request) (*http.Response, error) {
	started := time.Now()
	resp, err := txp.RoundTrip(req)
	rt := &HTTPRoundTripEvent{
		Failure:                 nil,         // set later
		Finished:                time.Time{}, // set later
		Method:                  req.Method,
		RequestHeaders:          s.cloneRequestHeaders(req),
		ResponseBody:            nil, // set later
		ResponseBodyIsTruncated: false,
		ResponseBodyLength:      0,
		ResponseHeaders:         nil, // set later
		Started:                 started,
		StatusCode:              0, // set later
		Transport:               txp.Network(),
		URL:                     req.URL.String(),
	}
	if err != nil {
		rt.Finished = time.Now()
		rt.Failure = err
		s.appendHTTPRoundTripEvent(rt)
		return nil, err
	}
	rt.StatusCode = int64(resp.StatusCode)
	rt.ResponseHeaders = resp.Header.Clone()
	r := io.LimitReader(resp.Body, maxBodySnapshotSize)
	body, err := netxlite.ReadAllContext(req.Context(), r)
	if err != nil {
		rt.Finished = time.Now()
		rt.Failure = err
		s.appendHTTPRoundTripEvent(rt)
		return nil, err
	}
	resp.Body = &archivalHTTPTransportBody{ // allow for reading again the whole body
		Reader: io.MultiReader(bytes.NewReader(body), resp.Body),
		Closer: resp.Body,
	}
	rt.ResponseBody = body
	rt.ResponseBodyLength = int64(len(body))
	rt.ResponseBodyIsTruncated = int64(len(body)) >= maxBodySnapshotSize
	rt.Finished = time.Now()
	s.appendHTTPRoundTripEvent(rt)
	return resp, nil
}

// cloneRequestHeaders ensure we include the Host header among the saved
// headers, which is what OONI should do, even though the Go transport is
// such that this header is added later when we're sending the request.
func (s *Saver) cloneRequestHeaders(req *http.Request) http.Header {
	header := req.Header.Clone()
	if req.Host != "" {
		header.Set("Host", req.Host)
	} else {
		header.Set("Host", req.URL.Host)
	}
	return header
}

type archivalHTTPTransportBody struct {
	io.Reader
	io.Closer
}

func (s *Saver) appendHTTPRoundTripEvent(ev *HTTPRoundTripEvent) {
	s.mu.Lock()
	s.trace.HTTPRoundTrip = append(s.trace.HTTPRoundTrip, ev)
	s.mu.Unlock()
}
