package h2quic

import (
	"bytes"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"golang.org/x/net/http2"
	"golang.org/x/net/http2/hpack"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Request", func() {
	var (
		rw           *requestWriter
		headerStream *mockStream
		decoder      *hpack.Decoder
	)

	BeforeEach(func() {
		headerStream = &mockStream{}
		rw = newRequestWriter(headerStream)
		decoder = hpack.NewDecoder(4096, func(hf hpack.HeaderField) {})
	})

	decode := func(p []byte) (*http2.HeadersFrame, map[string] /* HeaderField.Name */ string /* HeaderField.Value */) {
		framer := http2.NewFramer(nil, bytes.NewReader(p))
		frame, err := framer.ReadFrame()
		Expect(err).ToNot(HaveOccurred())
		headerFrame := frame.(*http2.HeadersFrame)
		fields, err := decoder.DecodeFull(headerFrame.HeaderBlockFragment())
		Expect(err).ToNot(HaveOccurred())
		values := make(map[string]string)
		for _, headerField := range fields {
			values[headerField.Name] = headerField.Value
		}
		return headerFrame, values
	}

	It("writes a GET request", func() {
		req, err := http.NewRequest("GET", "https://quic.clemente.io/index.html?foo=bar", nil)
		Expect(err).ToNot(HaveOccurred())
		rw.WriteRequest(req, 1337, true, false)
		headerFrame, headerFields := decode(headerStream.dataWritten.Bytes())
		Expect(headerFrame.StreamID).To(Equal(uint32(1337)))
		Expect(headerFrame.HasPriority()).To(BeTrue())
		Expect(headerFields).To(HaveKeyWithValue(":authority", "quic.clemente.io"))
		Expect(headerFields).To(HaveKeyWithValue(":method", "GET"))
		Expect(headerFields).To(HaveKeyWithValue(":path", "/index.html?foo=bar"))
		Expect(headerFields).To(HaveKeyWithValue(":scheme", "https"))
		Expect(headerFields).ToNot(HaveKey("accept-encoding"))
	})

	It("sets the EndStream header", func() {
		req, err := http.NewRequest("GET", "https://quic.clemente.io/", nil)
		Expect(err).ToNot(HaveOccurred())
		rw.WriteRequest(req, 1337, true, false)
		headerFrame, _ := decode(headerStream.dataWritten.Bytes())
		Expect(headerFrame.StreamEnded()).To(BeTrue())
	})

	It("doesn't set the EndStream header, if requested", func() {
		req, err := http.NewRequest("GET", "https://quic.clemente.io/", nil)
		Expect(err).ToNot(HaveOccurred())
		rw.WriteRequest(req, 1337, false, false)
		headerFrame, _ := decode(headerStream.dataWritten.Bytes())
		Expect(headerFrame.StreamEnded()).To(BeFalse())
	})

	It("requests gzip compression, if requested", func() {
		req, err := http.NewRequest("GET", "https://quic.clemente.io/index.html?foo=bar", nil)
		Expect(err).ToNot(HaveOccurred())
		rw.WriteRequest(req, 1337, true, true)
		_, headerFields := decode(headerStream.dataWritten.Bytes())
		Expect(headerFields).To(HaveKeyWithValue("accept-encoding", "gzip"))
	})

	It("writes a POST request", func() {
		form := url.Values{}
		form.Add("foo", "bar")
		req, err := http.NewRequest("POST", "https://quic.clemente.io/upload.html", strings.NewReader(form.Encode()))
		Expect(err).ToNot(HaveOccurred())
		rw.WriteRequest(req, 5, true, false)
		_, headerFields := decode(headerStream.dataWritten.Bytes())
		Expect(headerFields).To(HaveKeyWithValue(":method", "POST"))
		Expect(headerFields).To(HaveKey("content-length"))
		contentLength, err := strconv.Atoi(headerFields["content-length"])
		Expect(err).ToNot(HaveOccurred())
		Expect(contentLength).To(BeNumerically(">", 0))
	})

	It("sends cookies", func() {
		req, err := http.NewRequest("GET", "https://quic.clemente.io/", nil)
		Expect(err).ToNot(HaveOccurred())
		cookie1 := &http.Cookie{
			Name:  "Cookie #1",
			Value: "Value #1",
		}
		cookie2 := &http.Cookie{
			Name:  "Cookie #2",
			Value: "Value #2",
		}
		req.AddCookie(cookie1)
		req.AddCookie(cookie2)
		rw.WriteRequest(req, 11, true, false)
		_, headerFields := decode(headerStream.dataWritten.Bytes())
		// TODO(lclemente): Remove Or() once we drop support for Go 1.8.
		Expect(headerFields).To(Or(
			HaveKeyWithValue("cookie", "Cookie #1=Value #1; Cookie #2=Value #2"),
			HaveKeyWithValue("cookie", `Cookie #1="Value #1"; Cookie #2="Value #2"`),
		))
	})
})
