// 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 (
	"context"
	"fmt"
	"sync"

	"golang.org/x/net/quic"
)

// A Transport is an HTTP/3 transport.
//
// It does not manage a pool of connections,
// and therefore does not implement net/http.RoundTripper.
//
// TODO: Provide a way to register an HTTP/3 transport with a net/http.Transport's
// connection pool.
type Transport struct {
	// Endpoint is the QUIC endpoint used by connections created by the transport.
	// If unset, it is initialized by the first call to Dial.
	Endpoint *quic.Endpoint

	// Config is the QUIC configuration used for client connections.
	// The Config may be nil.
	//
	// Dial may clone and modify the Config.
	// The Config must not be modified after calling Dial.
	Config *quic.Config

	initOnce sync.Once
	initErr  error
}

func (tr *Transport) init() error {
	tr.initOnce.Do(func() {
		tr.Config = initConfig(tr.Config)
		if tr.Endpoint == nil {
			tr.Endpoint, tr.initErr = quic.Listen("udp", ":0", nil)
		}
	})
	return tr.initErr
}

// Dial creates a new HTTP/3 client connection.
func (tr *Transport) Dial(ctx context.Context, target string) (*ClientConn, error) {
	if err := tr.init(); err != nil {
		return nil, err
	}
	qconn, err := tr.Endpoint.Dial(ctx, "udp", target, tr.Config)
	if err != nil {
		return nil, err
	}
	return newClientConn(ctx, qconn)
}

// A ClientConn is a client HTTP/3 connection.
//
// Multiple goroutines may invoke methods on a ClientConn simultaneously.
type ClientConn struct {
	qconn *quic.Conn
	genericConn

	enc qpackEncoder
	dec qpackDecoder
}

func newClientConn(ctx context.Context, qconn *quic.Conn) (*ClientConn, error) {
	cc := &ClientConn{
		qconn: qconn,
	}
	cc.enc.init()

	// Create control stream and send SETTINGS frame.
	controlStream, err := newConnStream(ctx, cc.qconn, streamTypeControl)
	if err != nil {
		return nil, fmt.Errorf("http3: cannot create control stream: %v", err)
	}
	controlStream.writeSettings()
	controlStream.Flush()

	go cc.acceptStreams(qconn, cc)
	return cc, nil
}

// Close closes the connection.
// Any in-flight requests are canceled.
// Close does not wait for the peer to acknowledge the connection closing.
func (cc *ClientConn) Close() error {
	// Close the QUIC connection immediately with a status of NO_ERROR.
	cc.qconn.Abort(nil)

	// Return any existing error from the peer, but don't wait for it.
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	return cc.qconn.Wait(ctx)
}

func (cc *ClientConn) handleControlStream(st *stream) error {
	// "A SETTINGS frame MUST be sent as the first frame of each control stream [...]"
	// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4-2
	if err := st.readSettings(func(settingsType, settingsValue int64) error {
		switch settingsType {
		case settingsMaxFieldSectionSize:
			_ = settingsValue // TODO
		case settingsQPACKMaxTableCapacity:
			_ = settingsValue // TODO
		case settingsQPACKBlockedStreams:
			_ = settingsValue // TODO
		default:
			// Unknown settings types are ignored.
		}
		return nil
	}); err != nil {
		return err
	}

	for {
		ftype, err := st.readFrameHeader()
		if err != nil {
			return err
		}
		switch ftype {
		case frameTypeCancelPush:
			// "If a CANCEL_PUSH frame is received that references a push ID
			// greater than currently allowed on the connection,
			// this MUST be treated as a connection error of type H3_ID_ERROR."
			// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.3-7
			return &connectionError{
				code:    errH3IDError,
				message: "CANCEL_PUSH received when no MAX_PUSH_ID has been sent",
			}
		case frameTypeGoaway:
			// TODO: Wait for requests to complete before closing connection.
			return errH3NoError
		default:
			// Unknown frames are ignored.
			if err := st.discardUnknownFrame(ftype); err != nil {
				return err
			}
		}
	}
}

func (cc *ClientConn) handleEncoderStream(*stream) error {
	// TODO
	return nil
}

func (cc *ClientConn) handleDecoderStream(*stream) error {
	// TODO
	return nil
}

func (cc *ClientConn) handlePushStream(*stream) error {
	// "A client MUST treat receipt of a push stream as a connection error
	// of type H3_ID_ERROR when no MAX_PUSH_ID frame has been sent [...]"
	// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.6-3
	return &connectionError{
		code:    errH3IDError,
		message: "push stream created when no MAX_PUSH_ID has been sent",
	}
}

func (cc *ClientConn) handleRequestStream(st *stream) error {
	// "Clients MUST treat receipt of a server-initiated bidirectional
	// stream as a connection error of type H3_STREAM_CREATION_ERROR [...]"
	// https://www.rfc-editor.org/rfc/rfc9114.html#section-6.1-3
	return &connectionError{
		code:    errH3StreamCreationError,
		message: "server created bidirectional stream",
	}
}

// abort closes the connection with an error.
func (cc *ClientConn) abort(err error) {
	if e, ok := err.(*connectionError); ok {
		cc.qconn.Abort(&quic.ApplicationError{
			Code:   uint64(e.code),
			Reason: e.message,
		})
	} else {
		cc.qconn.Abort(err)
	}
}
