// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package http3

import (
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/textproto"
	"strings"
	"sync"

	"golang.org/x/net/http/httpguts"
)

// extractTrailerFromHeader extracts the "Trailer" header values from a header
// map, and populates a trailer map with those values as keys. The extracted
// header values will be canonicalized.
func extractTrailerFromHeader(header, trailer http.Header) {
	for _, names := range header["Trailer"] {
		names = textproto.TrimString(names)
		for name := range strings.SplitSeq(names, ",") {
			name = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(name))
			if !httpguts.ValidTrailerHeader(name) {
				continue
			}
			trailer[name] = nil
		}
	}
}

// A bodyWriter writes a request or response body to a stream
// as a series of DATA frames.
type bodyWriter struct {
	st      *stream
	remain  int64         // -1 when content-length is not known
	flush   bool          // flush the stream after every write
	name    string        // "request" or "response"
	trailer http.Header   // trailer headers that will be written once bodyWriter is closed.
	enc     *qpackEncoder // QPACK encoder used by the connection.
}

func (w *bodyWriter) write(ps ...[]byte) (n int, err error) {
	var size int64
	for _, p := range ps {
		size += int64(len(p))
	}
	// If write is called with empty byte slices, just return instead of
	// sending out a DATA frame containing nothing.
	if size == 0 {
		return 0, nil
	}
	if w.remain >= 0 && size > w.remain {
		return 0, &streamError{
			code:    errH3InternalError,
			message: w.name + " body longer than specified content length",
		}
	}
	w.st.writeVarint(int64(frameTypeData))
	w.st.writeVarint(size)
	for _, p := range ps {
		var n2 int
		n2, err = w.st.Write(p)
		n += n2
		if w.remain >= 0 {
			w.remain -= int64(n)
		}
		if err != nil {
			break
		}
	}
	if w.flush && err == nil {
		err = w.st.Flush()
	}
	if err != nil {
		err = fmt.Errorf("writing %v body: %w", w.name, err)
	}
	return n, err
}

func (w *bodyWriter) Write(p []byte) (n int, err error) {
	return w.write(p)
}

func (w *bodyWriter) Close() error {
	if w.remain > 0 {
		return errors.New(w.name + " body shorter than specified content length")
	}
	if len(w.trailer) > 0 {
		encTrailer := w.enc.encode(func(f func(itype indexType, name, value string)) {
			for name, values := range w.trailer {
				if !httpguts.ValidHeaderFieldName(name) {
					continue
				}
				for _, val := range values {
					if !httpguts.ValidHeaderFieldValue(val) {
						continue
					}
					f(mayIndex, name, val)
				}
			}
		})
		w.st.writeVarint(int64(frameTypeHeaders))
		w.st.writeVarint(int64(len(encTrailer)))
		w.st.Write(encTrailer)
	}
	if w.st != nil {
		w.st.CloseWrite()
	}
	return nil
}

// A bodyReader reads a request or response body from a stream.
type bodyReader struct {
	st *stream

	mu     sync.Mutex
	remain int64
	err    error
	// A map where the key represents the trailer header names we expect. If
	// there is a HEADERS frame after reading DATA frames to EOF, the value of
	// the headers will be written here. Keys in the map are assumed to be
	// canonicalized.
	// If filterTrailer is true, headers that are not already in the map will
	// be ignored; otherwise, all headers will be added to the map.
	trailer       http.Header
	filterTrailer bool
}

func (r *bodyReader) Read(p []byte) (n int, err error) {
	// The HTTP/1 and HTTP/2 implementations both permit concurrent reads from a body,
	// in the sense that the race detector won't complain.
	// Use a mutex here to provide the same behavior.
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.err != nil {
		return 0, r.err
	}
	defer func() {
		if err != nil {
			r.err = err
		}
	}()
	if r.st.lim == 0 {
		// We've finished reading the previous DATA frame, so end it.
		if err := r.st.endFrame(); err != nil {
			return 0, err
		}
	}
	// Read the next DATA frame header,
	// if we aren't already in the middle of one.
	for r.st.lim < 0 {
		ftype, err := r.st.readFrameHeader()
		if err == io.EOF && r.remain > 0 {
			return 0, &streamError{
				code:    errH3MessageError,
				message: "body shorter than content-length",
			}
		}
		if err != nil {
			return 0, err
		}
		switch ftype {
		case frameTypeData:
			if r.remain >= 0 && r.st.lim > r.remain {
				return 0, &streamError{
					code:    errH3MessageError,
					message: "body longer than content-length",
				}
			}
			// Fall out of the loop and process the frame body below.
		case frameTypeHeaders:
			// This HEADERS frame contains the message trailers.
			if r.remain > 0 {
				return 0, &streamError{
					code:    errH3MessageError,
					message: "body shorter than content-length",
				}
			}
			var dec qpackDecoder
			if err := dec.decode(r.st, func(_ indexType, name, value string) error {
				if r.trailer == nil {
					return nil
				}
				if !validWireHeaderFieldName(name) || !httpguts.ValidHeaderFieldValue(value) {
					return nil
				}
				name = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(name))
				if !r.filterTrailer {
					r.trailer.Add(name, value)
				} else if _, ok := r.trailer[name]; ok {
					r.trailer.Add(name, value)
				}
				return nil
			}); err != nil {
				return 0, err
			}
			if err := r.st.discardFrame(); err != nil {
				return 0, err
			}
			return 0, io.EOF
		default:
			if err := r.st.discardUnknownFrame(ftype); err != nil {
				return 0, err
			}
		}
	}
	// We are now reading the content of a DATA frame.
	// Fill the read buffer or read to the end of the frame,
	// whichever comes first.
	if int64(len(p)) > r.st.lim {
		p = p[:r.st.lim]
	}
	n, err = r.st.Read(p)
	if r.remain > 0 {
		r.remain -= int64(n)
	}
	return n, err
}

func (r *bodyReader) Close() error {
	// Unlike the HTTP/1 and HTTP/2 body readers (at the time of this comment being written),
	// calling Close concurrently with Read will interrupt the read.
	r.st.CloseRead()
	// Make sure that any data that has already been written to bodyReader
	// cannot be read after it has been closed.
	r.mu.Lock()
	defer r.mu.Unlock()
	r.err = net.ErrClosed
	r.remain = 0
	return nil
}
