package httpclient

import (
	"errors"
	"fmt"
	"io"
)

// Similar implementation to http/net MaxBytesReader
// https://pkg.go.dev/net/http#MaxBytesReader
// What's happening differently here, is that the field that
// is limited is the response and not the request, thus
// the error handling/message needed to be accurate.

// ErrResponseBodyTooLarge indicates response body is too large
var ErrResponseBodyTooLarge = errors.New("http: response body too large")

// MaxBytesReader is similar to io.LimitReader but is intended for
// limiting the size of incoming request bodies. In contrast to
// io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a
// non-EOF error for a Read beyond the limit, and closes the
// underlying reader when its Close method is called.
//
// MaxBytesReader prevents clients from accidentally or maliciously
// sending a large request and wasting server resources.
func MaxBytesReader(r io.ReadCloser, n int64) io.ReadCloser {
	return &maxBytesReader{r: r, remainingBytes: n, limit: n}
}

type maxBytesReader struct {
	r              io.ReadCloser // underlying reader
	remainingBytes int64         // max bytes remaining
	limit          int64         // the actual limit
	err            error         // sticky error
}

func (l *maxBytesReader) Read(p []byte) (n int, err error) {
	if l.err != nil {
		return 0, l.err
	}
	if len(p) == 0 {
		return 0, nil
	}
	// If they asked for a 32KB byte read but only 5 bytes are
	// remaining, no need to read 32KB. 6 bytes will answer the
	// question of the whether we hit the limit or go past it.
	if int64(len(p)) > l.remainingBytes+1 {
		p = p[:l.remainingBytes+1]
	}
	n, err = l.r.Read(p)

	if int64(n) <= l.remainingBytes {
		l.remainingBytes -= int64(n)
		l.err = err
		return n, err
	}

	n = int(l.remainingBytes)
	l.remainingBytes = 0

	l.err = fmt.Errorf("error: %w, response limit is set to: %d", ErrResponseBodyTooLarge, l.limit)
	return n, l.err
}

func (l *maxBytesReader) Close() error {
	return l.r.Close()
}
