package http3

import (
	"bytes"
	"context"
	"io"
	"math"
	"net/http"

	mockquic "github.com/quic-go/quic-go/internal/mocks/quic"
	"github.com/quic-go/quic-go/internal/protocol"
	"github.com/quic-go/quic-go/internal/qerr"

	"github.com/quic-go/qpack"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"go.uber.org/mock/gomock"
)

func getDataFrame(data []byte) []byte {
	b := (&dataFrame{Length: uint64(len(data))}).Append(nil)
	return append(b, data...)
}

var _ = Describe("Stream", func() {
	Context("reading", func() {
		var (
			str           Stream
			qstr          *mockquic.MockStream
			buf           *bytes.Buffer
			errorCbCalled bool
		)

		BeforeEach(func() {
			buf = &bytes.Buffer{}
			errorCbCalled = false
			qstr = mockquic.NewMockStream(mockCtrl)
			qstr.EXPECT().Write(gomock.Any()).DoAndReturn(buf.Write).AnyTimes()
			qstr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
			conn := mockquic.NewMockEarlyConnection(mockCtrl)
			conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(qerr.ApplicationErrorCode, string) error {
				errorCbCalled = true
				return nil
			}).AnyTimes()
			str = newStream(qstr, newConnection(context.Background(), conn, false, protocol.PerspectiveClient, nil), nil)
		})

		It("reads DATA frames in a single run", func() {
			buf.Write(getDataFrame([]byte("foobar")))
			b := make([]byte, 6)
			n, err := str.Read(b)
			Expect(err).ToNot(HaveOccurred())
			Expect(n).To(Equal(6))
			Expect(b).To(Equal([]byte("foobar")))
		})

		It("reads DATA frames in multiple runs", func() {
			buf.Write(getDataFrame([]byte("foobar")))
			b := make([]byte, 3)
			n, err := str.Read(b)
			Expect(err).ToNot(HaveOccurred())
			Expect(n).To(Equal(3))
			Expect(b).To(Equal([]byte("foo")))
			n, err = str.Read(b)
			Expect(err).ToNot(HaveOccurred())
			Expect(n).To(Equal(3))
			Expect(b).To(Equal([]byte("bar")))
		})

		It("reads DATA frames into too large buffers", func() {
			buf.Write(getDataFrame([]byte("foobar")))
			b := make([]byte, 10)
			n, err := str.Read(b)
			Expect(err).ToNot(HaveOccurred())
			Expect(n).To(Equal(6))
			Expect(b[:n]).To(Equal([]byte("foobar")))
		})

		It("reads DATA frames into too large buffers, in multiple runs", func() {
			buf.Write(getDataFrame([]byte("foobar")))
			b := make([]byte, 4)
			n, err := str.Read(b)
			Expect(err).ToNot(HaveOccurred())
			Expect(n).To(Equal(4))
			Expect(b).To(Equal([]byte("foob")))
			n, err = str.Read(b)
			Expect(err).ToNot(HaveOccurred())
			Expect(n).To(Equal(2))
			Expect(b[:n]).To(Equal([]byte("ar")))
		})

		It("reads multiple DATA frames", func() {
			buf.Write(getDataFrame([]byte("foo")))
			buf.Write(getDataFrame([]byte("bar")))
			b := make([]byte, 6)
			n, err := str.Read(b)
			Expect(err).ToNot(HaveOccurred())
			Expect(n).To(Equal(3))
			Expect(b[:n]).To(Equal([]byte("foo")))
			n, err = str.Read(b)
			Expect(err).ToNot(HaveOccurred())
			Expect(n).To(Equal(3))
			Expect(b[:n]).To(Equal([]byte("bar")))
		})

		It("skips HEADERS frames", func() {
			b := getDataFrame([]byte("foo"))
			b = (&headersFrame{Length: 10}).Append(b)
			b = append(b, make([]byte, 10)...)
			b = append(b, getDataFrame([]byte("bar"))...)
			buf.Write(b)
			r := make([]byte, 6)
			n, err := io.ReadFull(str, r)
			Expect(err).ToNot(HaveOccurred())
			Expect(n).To(Equal(6))
			Expect(r).To(Equal([]byte("foobar")))
		})

		It("errors when it can't parse the frame", func() {
			buf.Write([]byte("invalid"))
			_, err := str.Read([]byte{0})
			Expect(err).To(HaveOccurred())
		})

		It("errors on unexpected frames, and calls the error callback", func() {
			b := (&settingsFrame{}).Append(nil)
			buf.Write(b)
			_, err := str.Read([]byte{0})
			Expect(err).To(MatchError("peer sent an unexpected frame: *http3.settingsFrame"))
			Expect(errorCbCalled).To(BeTrue())
		})
	})

	Context("writing", func() {
		It("writes data frames", func() {
			buf := &bytes.Buffer{}
			qstr := mockquic.NewMockStream(mockCtrl)
			qstr.EXPECT().Write(gomock.Any()).DoAndReturn(buf.Write).AnyTimes()
			str := newStream(qstr, nil, nil)
			str.Write([]byte("foo"))
			str.Write([]byte("foobar"))

			fp := frameParser{r: buf}
			f, err := fp.ParseNext()
			Expect(err).ToNot(HaveOccurred())
			Expect(f).To(Equal(&dataFrame{Length: 3}))
			b := make([]byte, 3)
			_, err = io.ReadFull(buf, b)
			Expect(err).ToNot(HaveOccurred())
			Expect(b).To(Equal([]byte("foo")))

			fp = frameParser{r: buf}
			f, err = fp.ParseNext()
			Expect(err).ToNot(HaveOccurred())
			Expect(f).To(Equal(&dataFrame{Length: 6}))
			b = make([]byte, 6)
			_, err = io.ReadFull(buf, b)
			Expect(err).ToNot(HaveOccurred())
			Expect(b).To(Equal([]byte("foobar")))
		})
	})
})

var _ = Describe("Request Stream", func() {
	var str *requestStream
	var qstr *mockquic.MockStream

	BeforeEach(func() {
		qstr = mockquic.NewMockStream(mockCtrl)
		requestWriter := newRequestWriter()
		conn := mockquic.NewMockEarlyConnection(mockCtrl)
		str = newRequestStream(
			newStream(qstr, newConnection(context.Background(), conn, false, protocol.PerspectiveClient, nil), nil),
			requestWriter,
			make(chan struct{}),
			qpack.NewDecoder(func(qpack.HeaderField) {}),
			true,
			math.MaxUint64,
		)
	})

	It("refuses to read before having read the response", func() {
		_, err := str.Read(make([]byte, 100))
		Expect(err).To(MatchError("http3: invalid use of RequestStream.Read: need to call ReadResponse first"))
	})

	It("prevents duplicate calls to SendRequestHeader", func() {
		req, err := http.NewRequest(http.MethodGet, "https://quic-go.net", nil)
		Expect(err).ToNot(HaveOccurred())
		qstr.EXPECT().Write(gomock.Any()).AnyTimes()
		Expect(str.SendRequestHeader(req)).To(Succeed())
		Expect(str.SendRequestHeader(req)).To(MatchError("http3: invalid duplicate use of SendRequestHeader"))
	})

	It("reads after the response", func() {
		req, err := http.NewRequest(http.MethodGet, "https://quic-go.net", nil)
		Expect(err).ToNot(HaveOccurred())
		qstr.EXPECT().Write(gomock.Any()).AnyTimes()
		Expect(str.SendRequestHeader(req)).To(Succeed())

		buf := bytes.NewBuffer(encodeResponse(200))
		buf.Write((&dataFrame{Length: 6}).Append(nil))
		buf.Write([]byte("foobar"))
		qstr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
		rsp, err := str.ReadResponse()
		Expect(err).ToNot(HaveOccurred())
		Expect(rsp.StatusCode).To(Equal(200))
		b := make([]byte, 10)
		n, err := str.Read(b)
		Expect(err).ToNot(HaveOccurred())
		Expect(n).To(Equal(6))
		Expect(b[:n]).To(Equal([]byte("foobar")))
	})
})
