package masque

import (
	"context"
	"crypto/tls"
	"errors"
	"net"
	"time"

	"github.com/go-gost/core/logger"
	"github.com/quic-go/quic-go"
	"github.com/quic-go/quic-go/http3"
)

var errReadWriteNotSupported = errors.New("masque: direct Read/Write not supported on MasqueConn, use GetRequestStream")

// Client manages HTTP/3 connections for MASQUE proxying.
// It wraps a QUIC connection and HTTP/3 client, following the session pattern
// used by other gost dialers (ssh, quic, mws, etc.).
type Client struct {
	host       string
	addr       string
	transport  *http3.Transport
	quicConn   *quic.Conn
	clientConn *http3.ClientConn
	dialer     func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error)
	log        logger.Logger
}

// IsClosed returns true if the underlying connection is closed.
// This follows the pattern used by other gost session wrappers.
func (c *Client) IsClosed() bool {
	if c.clientConn == nil {
		return true
	}
	select {
	case <-c.clientConn.Context().Done():
		return true
	default:
	}
	return false
}

// Close closes the client connection.
func (c *Client) Close() error {
	if c.quicConn != nil {
		return (*c.quicConn).CloseWithError(0, "closed")
	}
	return nil
}

// Dial establishes an HTTP/3 connection to the proxy server.
// It returns a MasqueConn that can be used by the MASQUE connector.
// Following the QUIC dialer pattern, this opens a request stream immediately
// to detect dead connections and allow cache invalidation.
func (c *Client) Dial(ctx context.Context, addr string) (net.Conn, error) {
	// Create connection if we don't have one (caller checks IsClosed first)
	if c.clientConn == nil {
		// Use the transport's Dial function to establish QUIC connection
		quicConn, err := c.dialer(ctx, addr, c.transport.TLSClientConfig, c.transport.QUICConfig)
		if err != nil {
			return nil, err
		}
		c.quicConn = quicConn

		// Create HTTP/3 client connection on top of QUIC
		c.clientConn = c.transport.NewClientConn(quicConn)
	}

	// Open request stream NOW (following QUIC dialer pattern).
	// This allows dead connection detection - if this fails, caller can invalidate cache.
	reqStream, err := c.clientConn.OpenRequestStream(ctx)
	if err != nil {
		// Connection is dead - clear our state so next dial creates fresh connection
		c.clientConn = nil
		c.quicConn = nil
		return nil, err
	}

	// Return a connection wrapper with pre-opened stream
	return &MasqueConn{
		clientConn: c.clientConn,
		reqStream:  reqStream,
		host:       c.host,
		log:        c.log,
	}, nil
}

// MasqueConn wraps an HTTP/3 client connection for use with the MASQUE connector.
// It implements net.Conn for compatibility with the gost connector interface,
// but the actual data transfer happens via datagrams through the pre-opened RequestStream.
type MasqueConn struct {
	clientConn *http3.ClientConn
	reqStream  *http3.RequestStream // Pre-opened stream for CONNECT-UDP
	host       string
	log        logger.Logger
}

// GetRequestStream returns the pre-opened HTTP/3 request stream.
// The connector uses this for the CONNECT-UDP handshake.
func (c *MasqueConn) GetRequestStream() *http3.RequestStream {
	return c.reqStream
}

// GetHost returns the proxy host.
func (c *MasqueConn) GetHost() string {
	return c.host
}

// Read implements net.Conn but is not used for MASQUE.
// The actual data transfer happens via datagrams or the request stream.
func (c *MasqueConn) Read(b []byte) (n int, err error) {
	return 0, errReadWriteNotSupported
}

// Write implements net.Conn but is not used for MASQUE.
// The actual data transfer happens via datagrams or the request stream.
func (c *MasqueConn) Write(b []byte) (n int, err error) {
	return 0, errReadWriteNotSupported
}

// Close closes the connection.
func (c *MasqueConn) Close() error {
	// Don't close the underlying clientConn as it's shared/pooled
	return nil
}

// LocalAddr returns the local network address.
func (c *MasqueConn) LocalAddr() net.Addr {
	return &net.UDPAddr{}
}

// RemoteAddr returns the remote network address.
func (c *MasqueConn) RemoteAddr() net.Addr {
	return &net.UDPAddr{}
}

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

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

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