package httptransport

import (
	"io"
	"net/http"

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

// ByteCountingTransport is a RoundTripper that counts bytes.
type ByteCountingTransport struct {
	RoundTripper
	Counter *bytecounter.Counter
}

// RoundTrip implements RoundTripper.RoundTrip
func (txp ByteCountingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	if req.Body != nil {
		req.Body = byteCountingBody{
			ReadCloser: req.Body, Account: txp.Counter.CountBytesSent}
	}
	txp.estimateRequestMetadata(req)
	resp, err := txp.RoundTripper.RoundTrip(req)
	if err != nil {
		return nil, err
	}
	txp.estimateResponseMetadata(resp)
	resp.Body = byteCountingBody{
		ReadCloser: resp.Body, Account: txp.Counter.CountBytesReceived}
	return resp, nil
}

func (txp ByteCountingTransport) estimateRequestMetadata(req *http.Request) {
	txp.Counter.CountBytesSent(len(req.Method))
	txp.Counter.CountBytesSent(len(req.URL.String()))
	for key, values := range req.Header {
		for _, value := range values {
			txp.Counter.CountBytesSent(len(key))
			txp.Counter.CountBytesSent(len(": "))
			txp.Counter.CountBytesSent(len(value))
			txp.Counter.CountBytesSent(len("\r\n"))
		}
	}
	txp.Counter.CountBytesSent(len("\r\n"))
}

func (txp ByteCountingTransport) estimateResponseMetadata(resp *http.Response) {
	txp.Counter.CountBytesReceived(len(resp.Status))
	for key, values := range resp.Header {
		for _, value := range values {
			txp.Counter.CountBytesReceived(len(key))
			txp.Counter.CountBytesReceived(len(": "))
			txp.Counter.CountBytesReceived(len(value))
			txp.Counter.CountBytesReceived(len("\r\n"))
		}
	}
	txp.Counter.CountBytesReceived(len("\r\n"))
}

type byteCountingBody struct {
	io.ReadCloser
	Account func(int)
}

func (r byteCountingBody) Read(p []byte) (int, error) {
	count, err := r.ReadCloser.Read(p)
	if count > 0 {
		r.Account(count)
	}
	return count, err
}

var _ RoundTripper = ByteCountingTransport{}
