package masque

import (
	"context"
	"io"
	"net"
	"sync"
	"time"

	"github.com/quic-go/quic-go/http3"
)

// DatagramStreamer is an interface for sending and receiving HTTP/3 datagrams.
// Both http3.Stream and http3.RequestStream implement this interface.
type DatagramStreamer interface {
	SendDatagram(b []byte) error
	ReceiveDatagram(ctx context.Context) ([]byte, error)
}

// DatagramConn wraps an HTTP/3 stream's datagram methods as net.PacketConn.
// This allows UDP packets to be tunneled over HTTP/3 datagrams per RFC 9297/9298.
type DatagramConn struct {
	stream     DatagramStreamer
	closer     io.Closer // Optional closer for the underlying stream
	localAddr  net.Addr
	remoteAddr net.Addr
	closed     chan struct{}
	closeOnce  sync.Once

	mu           sync.RWMutex
	readDeadline time.Time
}

// NewDatagramConn creates a new DatagramConn wrapping an HTTP/3 stream.
func NewDatagramConn(stream *http3.Stream, laddr, raddr net.Addr) *DatagramConn {
	return &DatagramConn{
		stream:     stream,
		localAddr:  laddr,
		remoteAddr: raddr,
		closed:     make(chan struct{}),
	}
}

// NewDatagramConnFromRequestStream creates a new DatagramConn wrapping an HTTP/3 request stream.
// This is used by the client-side connector. The stream will be closed when Close() is called.
func NewDatagramConnFromRequestStream(stream *http3.RequestStream, laddr, raddr net.Addr) *DatagramConn {
	return &DatagramConn{
		stream:     stream,
		closer:     stream, // RequestStream implements io.Closer
		localAddr:  laddr,
		remoteAddr: raddr,
		closed:     make(chan struct{}),
	}
}

// ReadFrom reads a UDP datagram from the HTTP/3 stream.
// Per RFC 9297, HTTP datagrams have a context ID prefix.
// For CONNECT-UDP (RFC 9298), the context ID is 0.
func (c *DatagramConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
	select {
	case <-c.closed:
		return 0, nil, net.ErrClosed
	default:
	}

	ctx := context.Background()
	c.mu.RLock()
	deadline := c.readDeadline
	c.mu.RUnlock()

	if !deadline.IsZero() {
		var cancel context.CancelFunc
		ctx, cancel = context.WithDeadline(ctx, deadline)
		defer cancel()
	}

	data, err := c.stream.ReceiveDatagram(ctx)
	if err != nil {
		return 0, nil, err
	}

	if len(data) == 0 {
		return 0, c.remoteAddr, nil
	}

	// Per RFC 9297: datagram format is context-id (varint) + payload.
	// For CONNECT-UDP (RFC 9298), the context ID is always 0.
	// QUIC varints encode 0 as a single byte 0x00.
	// NOTE: Only context ID 0 is supported. Non-zero context IDs would
	// require full QUIC varint decoding.
	if data[0] == 0x00 {
		data = data[1:]
	}

	n = copy(b, data)
	return n, c.remoteAddr, nil
}

// WriteTo sends a UDP datagram via the HTTP/3 stream.
// Per RFC 9297, we prepend the context ID (0x00 for CONNECT-UDP).
func (c *DatagramConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
	select {
	case <-c.closed:
		return 0, net.ErrClosed
	default:
	}

	// Prepend context ID (0x00 for CONNECT-UDP context ID 0 per RFC 9298)
	datagram := make([]byte, 1+len(b))
	datagram[0] = 0x00 // Context ID = 0
	copy(datagram[1:], b)

	if err := c.stream.SendDatagram(datagram); err != nil {
		return 0, err
	}

	return len(b), nil
}

// Read reads data from the connection (net.Conn interface).
func (c *DatagramConn) Read(b []byte) (n int, err error) {
	n, _, err = c.ReadFrom(b)
	return
}

// Write writes data to the connection (net.Conn interface).
func (c *DatagramConn) Write(b []byte) (n int, err error) {
	return c.WriteTo(b, c.remoteAddr)
}

// Close closes the datagram connection and the underlying stream.
func (c *DatagramConn) Close() error {
	var err error
	c.closeOnce.Do(func() {
		close(c.closed)
		if c.closer != nil {
			err = c.closer.Close()
		}
	})
	return err
}

// LocalAddr returns the local network address.
func (c *DatagramConn) LocalAddr() net.Addr {
	return c.localAddr
}

// RemoteAddr returns the remote network address.
func (c *DatagramConn) RemoteAddr() net.Addr {
	return c.remoteAddr
}

// SetDeadline sets the read and write deadlines.
func (c *DatagramConn) SetDeadline(t time.Time) error {
	c.SetReadDeadline(t)
	c.SetWriteDeadline(t)
	return nil
}

// SetReadDeadline sets the read deadline.
func (c *DatagramConn) SetReadDeadline(t time.Time) error {
	c.mu.Lock()
	c.readDeadline = t
	c.mu.Unlock()
	return nil
}

// SetWriteDeadline sets the write deadline.
// Note: Write deadlines are not used for HTTP/3 datagrams as SendDatagram is non-blocking.
func (c *DatagramConn) SetWriteDeadline(t time.Time) error {
	return nil
}

// Ensure DatagramConn implements both net.Conn and net.PacketConn
var (
	_ net.Conn       = (*DatagramConn)(nil)
	_ net.PacketConn = (*DatagramConn)(nil)
)

// StreamReadWriter is an interface for reading and writing to HTTP/3 streams.
// Both http3.Stream and http3.RequestStream implement this interface.
type StreamReadWriter interface {
	io.Reader
	io.Writer
	io.Closer
	SetDeadline(t time.Time) error
	SetReadDeadline(t time.Time) error
	SetWriteDeadline(t time.Time) error
}

// StreamConn wraps an HTTP/3 stream as net.Conn for TCP tunneling (RFC 9114).
// Unlike DatagramConn which uses HTTP/3 datagrams, StreamConn reads/writes
// directly to the HTTP/3 stream body for reliable, ordered byte streams.
type StreamConn struct {
	stream     StreamReadWriter
	localAddr  net.Addr
	remoteAddr net.Addr
	closed     chan struct{}
	closeOnce  sync.Once
}

// NewStreamConn creates a new StreamConn wrapping an HTTP/3 stream (server-side).
// This is used for TCP CONNECT tunneling where data flows through the stream body.
func NewStreamConn(stream *http3.Stream, laddr, raddr net.Addr) *StreamConn {
	return &StreamConn{
		stream:     stream,
		localAddr:  laddr,
		remoteAddr: raddr,
		closed:     make(chan struct{}),
	}
}

// NewStreamConnFromRequestStream creates a new StreamConn wrapping an HTTP/3 request stream (client-side).
// This is used for TCP CONNECT tunneling where data flows through the stream body.
func NewStreamConnFromRequestStream(stream *http3.RequestStream, laddr, raddr net.Addr) *StreamConn {
	return &StreamConn{
		stream:     stream,
		localAddr:  laddr,
		remoteAddr: raddr,
		closed:     make(chan struct{}),
	}
}

// Read reads data from the HTTP/3 stream body.
func (c *StreamConn) Read(b []byte) (n int, err error) {
	select {
	case <-c.closed:
		return 0, net.ErrClosed
	default:
	}
	return c.stream.Read(b)
}

// Write writes data to the HTTP/3 stream body.
func (c *StreamConn) Write(b []byte) (n int, err error) {
	select {
	case <-c.closed:
		return 0, net.ErrClosed
	default:
	}
	return c.stream.Write(b)
}

// Close closes the stream connection.
func (c *StreamConn) Close() error {
	var err error
	c.closeOnce.Do(func() {
		close(c.closed)
		err = c.stream.Close()
	})
	return err
}

// LocalAddr returns the local network address.
func (c *StreamConn) LocalAddr() net.Addr {
	return c.localAddr
}

// RemoteAddr returns the remote network address.
func (c *StreamConn) RemoteAddr() net.Addr {
	return c.remoteAddr
}

// SetDeadline sets the read and write deadlines.
func (c *StreamConn) SetDeadline(t time.Time) error {
	return c.stream.SetDeadline(t)
}

// SetReadDeadline sets the read deadline.
func (c *StreamConn) SetReadDeadline(t time.Time) error {
	return c.stream.SetReadDeadline(t)
}

// SetWriteDeadline sets the write deadline.
func (c *StreamConn) SetWriteDeadline(t time.Time) error {
	return c.stream.SetWriteDeadline(t)
}

// Ensure StreamConn implements net.Conn
var _ net.Conn = (*StreamConn)(nil)
