package pool_test

import (
	"bufio"
	"context"
	"sync/atomic"
	"unsafe"

	. "github.com/bsm/ginkgo/v2"
	. "github.com/bsm/gomega"

	"github.com/redis/go-redis/v9/internal/pool"
	"github.com/redis/go-redis/v9/internal/proto"
)

var _ = Describe("Buffer Size Configuration", func() {
	var connPool *pool.ConnPool
	ctx := context.Background()

	AfterEach(func() {
		if connPool != nil {
			connPool.Close()
		}
	})

	It("should use default buffer sizes when not specified", func() {
		connPool = pool.NewConnPool(&pool.Options{
			Dialer:             dummyDialer,
			PoolSize:           int32(1),
			MaxConcurrentDials: 1,
			PoolTimeout:        1000,
		})

		cn, err := connPool.NewConn(ctx)
		Expect(err).NotTo(HaveOccurred())
		defer connPool.CloseConn(cn)

		// Check that default buffer sizes are used (32KiB)
		writerBufSize := getWriterBufSizeUnsafe(cn)
		readerBufSize := getReaderBufSizeUnsafe(cn)

		Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size
		Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size
	})

	It("should use custom buffer sizes when specified", func() {
		customReadSize := 32 * 1024  // 32KB
		customWriteSize := 64 * 1024 // 64KB

		connPool = pool.NewConnPool(&pool.Options{
			Dialer:             dummyDialer,
			PoolSize:           int32(1),
			MaxConcurrentDials: 1,
			PoolTimeout:        1000,
			ReadBufferSize:     customReadSize,
			WriteBufferSize:    customWriteSize,
		})

		cn, err := connPool.NewConn(ctx)
		Expect(err).NotTo(HaveOccurred())
		defer connPool.CloseConn(cn)

		// Check that custom buffer sizes are used
		writerBufSize := getWriterBufSizeUnsafe(cn)
		readerBufSize := getReaderBufSizeUnsafe(cn)

		Expect(writerBufSize).To(Equal(customWriteSize))
		Expect(readerBufSize).To(Equal(customReadSize))
	})

	It("should handle zero buffer sizes by using defaults", func() {
		connPool = pool.NewConnPool(&pool.Options{
			Dialer:             dummyDialer,
			PoolSize:           int32(1),
			MaxConcurrentDials: 1,
			PoolTimeout:        1000,
			ReadBufferSize:     0, // Should use default
			WriteBufferSize:    0, // Should use default
		})

		cn, err := connPool.NewConn(ctx)
		Expect(err).NotTo(HaveOccurred())
		defer connPool.CloseConn(cn)

		// Check that default buffer sizes are used (32KiB)
		writerBufSize := getWriterBufSizeUnsafe(cn)
		readerBufSize := getReaderBufSizeUnsafe(cn)

		Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size
		Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size
	})

	It("should use 32KiB default buffer sizes for standalone NewConn", func() {
		// Test that NewConn (without pool) also uses 32KiB buffers
		netConn := newDummyConn()
		cn := pool.NewConn(netConn)
		defer cn.Close()

		writerBufSize := getWriterBufSizeUnsafe(cn)
		readerBufSize := getReaderBufSizeUnsafe(cn)

		Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size
		Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size
	})

	It("should use 32KiB defaults even when pool is created directly without buffer sizes", func() {
		// Test the scenario where someone creates a pool directly (like in tests)
		// without setting ReadBufferSize and WriteBufferSize
		connPool = pool.NewConnPool(&pool.Options{
			Dialer:             dummyDialer,
			PoolSize:           int32(1),
			MaxConcurrentDials: 1,
			PoolTimeout:        1000,
			// ReadBufferSize and WriteBufferSize are not set (will be 0)
		})

		cn, err := connPool.NewConn(ctx)
		Expect(err).NotTo(HaveOccurred())
		defer connPool.CloseConn(cn)

		// Should still get 32KiB defaults because NewConnPool sets them
		writerBufSize := getWriterBufSizeUnsafe(cn)
		readerBufSize := getReaderBufSizeUnsafe(cn)

		Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size
		Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size
	})
})

// Helper functions to extract buffer sizes using unsafe pointers
// The struct layout must match pool.Conn exactly to avoid checkptr violations.
// checkptr is Go's pointer safety checker, which ensures that unsafe pointer
// conversions are valid. If the struct layouts do not match exactly, this can
// cause runtime panics or incorrect memory access due to invalid pointer dereferencing.
func getWriterBufSizeUnsafe(cn *pool.Conn) int {
	cnPtr := (*struct {
		id            uint64       // First field in pool.Conn
		usedAt        atomic.Int64 // Second field (atomic)
		lastPutAt     atomic.Int64 // Third field (atomic)
		netConnAtomic interface{}  // atomic.Value (interface{} has same size)
		rd            *proto.Reader
		bw            *bufio.Writer
		wr            *proto.Writer
		// We only need fields up to bw, so we can stop here
	})(unsafe.Pointer(cn))

	if cnPtr.bw == nil {
		return -1
	}

	// bufio.Writer internal structure
	bwPtr := (*struct {
		err error
		buf []byte
		n   int
		wr  interface{}
	})(unsafe.Pointer(cnPtr.bw))

	return len(bwPtr.buf)
}

func getReaderBufSizeUnsafe(cn *pool.Conn) int {
	cnPtr := (*struct {
		id            uint64       // First field in pool.Conn
		usedAt        atomic.Int64 // Second field (atomic)
		lastPutAt     atomic.Int64 // Third field (atomic)
		netConnAtomic interface{}  // atomic.Value (interface{} has same size)
		rd            *proto.Reader
		bw            *bufio.Writer
		wr            *proto.Writer
		// We only need fields up to rd, so we can stop here
	})(unsafe.Pointer(cn))

	if cnPtr.rd == nil {
		return -1
	}

	// proto.Reader internal structure
	rdPtr := (*struct {
		rd *bufio.Reader
	})(unsafe.Pointer(cnPtr.rd))

	if rdPtr.rd == nil {
		return -1
	}

	// bufio.Reader internal structure
	bufReaderPtr := (*struct {
		buf          []byte
		rd           interface{}
		r, w         int
		err          error
		lastByte     int
		lastRuneSize int
	})(unsafe.Pointer(rdPtr.rd))

	return len(bufReaderPtr.buf)
}
