/*
 *
 * Copyright 2026 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package readyreader_test

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"net"
	"runtime"
	"strings"
	"testing"
	"time"

	"google.golang.org/grpc/internal/grpctest"
	"google.golang.org/grpc/internal/testutils"
	"google.golang.org/grpc/internal/transport/readyreader"
	"google.golang.org/grpc/mem"
)

var (
	defaultTestTimeout      = 10 * time.Second
	defaultTestShortTimeout = 10 * time.Millisecond
)

type s struct {
	grpctest.Tester
}

func Test(t *testing.T) {
	grpctest.RunSubTests(t, s{})
}

// TestReadyReader_NonRawConn verifies that ReadOnReady correctly reads data
// from a net.Conn that doesn't support non-memory-pinning reads.
func (s) TestReadyReader_NonRawConn(t *testing.T) {
	data := []byte("hello world")
	reader, writer := net.Pipe()
	go writer.Write(data)

	pool := mem.DefaultBufferPool()
	readyReader := readyreader.New(reader)

	bufHandle, n, err := readyReader.ReadOnReady(1024, pool)
	if err != nil {
		t.Fatalf("ReadOnReady() failed: %v", err)
	}
	defer pool.Put(bufHandle)

	if n != len(data) {
		t.Errorf("n = %d; want %d", n, len(data))
	}
	if !bytes.Equal((*bufHandle)[:n], data) {
		t.Errorf("Read data = %s; want %s", string((*bufHandle)[:n]), string(data))
	}
}

func (s) TestReadyReader_EOF(t *testing.T) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		t.Fatalf("net.Listen failed: %v", err)
	}
	defer ln.Close()

	data := []byte("hello")
	go func() {
		conn, err := ln.Accept()
		if err != nil {
			return
		}
		if _, err := conn.Write(data); err != nil {
			t.Errorf("Failed to write data: %v", err)
			return
		}
		conn.Close()
	}()

	conn, err := net.Dial("tcp", ln.Addr().String())
	if err != nil {
		t.Fatalf("net.Dial failed: %v", err)
	}
	defer conn.Close()

	pool := mem.DefaultBufferPool()
	rr := readyreader.New(conn)
	res, _, err := rr.ReadOnReady(len(data), pool)
	if err != nil {
		t.Errorf("Failed to read: %v", err)
		return
	}

	if !bytes.Equal(*res, data) {
		t.Errorf("Read data = %s; want %s", string(*res), string(data))
	}
	pool.Put(res)

	// Since the server closes the TCP connection, the next read should return
	// an io.EOF.
	res, _, err = rr.ReadOnReady(len(data), pool)
	if err != io.EOF {
		t.Errorf("Read after server connection close returned err = %v; want io.EOF", err)
	}
	if res != nil {
		t.Error("ReadOnReady() returned non-nil buffer.")
	}
}

// Tests the behavior of readers wrapping TCP connections. It ensures that read
// operations do not hold onto an allocated buffer while the connection is idle.
// Buffers should only be permanently allocated from the pool when data is
// actually ready on the socket, optimizing memory usage for idle connections.
func (s) TestReadyReader_TCP(t *testing.T) {
	if runtime.GOOS != "linux" {
		t.Skip("This test is only applicable for Linux, as RawConn functionality is not implemented for non-linux platforms.")
	}
	tests := []struct {
		name string
		read func(conn net.Conn, pool *trackingBufferPool, readBufSize int) ([]byte, error)
	}{
		{
			name: "ReadyReader",
			read: func(conn net.Conn, pool *trackingBufferPool, readBufSize int) ([]byte, error) {
				rr := readyreader.New(conn)
				bufHandle, n, err := rr.ReadOnReady(readBufSize, pool)
				if err != nil {
					return nil, err
				}
				defer pool.Put(bufHandle)
				res := make([]byte, n)
				copy(res, (*bufHandle)[:n])
				return res, nil
			},
		},
		{
			name: "BufReadyReader",
			read: func(conn net.Conn, pool *trackingBufferPool, readBufSize int) ([]byte, error) {
				rr := readyreader.New(conn)
				bufRR := readyreader.NewBuffered(rr, readBufSize, pool)
				buf := make([]byte, 100)
				n, err := bufRR.Read(buf)
				if err != nil {
					return nil, err
				}
				return buf[:n], nil
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			ctx, cancel := context.WithTimeout(t.Context(), defaultTestTimeout)
			defer cancel()
			ln, err := net.Listen("tcp", "localhost:0")
			if err != nil {
				t.Fatalf("net.Listen failed: %v", err)
			}
			defer ln.Close()

			data := []byte("hello tcp delayed")
			connCh := make(chan net.Conn)
			go func() {
				conn, err := ln.Accept()
				if err != nil {
					return
				}
				connCh <- conn
			}()

			conn, err := net.Dial("tcp", ln.Addr().String())
			if err != nil {
				t.Fatalf("net.Dial failed: %v", err)
			}
			defer conn.Close()

			serverConn := <-connCh
			defer serverConn.Close()

			pool := newTrackingPool(mem.DefaultBufferPool())
			const readBufSize = 1024

			resCh := make(chan []byte)

			go func() {
				res, err := tc.read(conn, pool, readBufSize)
				if err != nil {
					t.Errorf("Failed to read: %v", err)
					return
				}
				resCh <- res
			}()

			// Verify that no read buffer is allocated for a short while. If it
			// is allocated (e.g. for a probe), it must be returned immediately.
			sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
			defer sCancel()
			if n, err := pool.requestChan.Receive(sCtx); err == nil {
				if n != readBufSize {
					t.Fatalf("Unexpected request buffer size: got %d, want %d", n, readBufSize)
				}
				if n, err := pool.putChan.Receive(ctx); err != nil {
					t.Fatal("Read buffer allocated and NOT returned while idle.")
				} else if n != readBufSize {
					t.Fatalf("Unexpected returned buffer size: got %d, want %d", n, readBufSize)
				}
			}

			// Write data to unblock.
			serverConn.Write(data)

			if n, err := pool.requestChan.Receive(ctx); err != nil {
				t.Fatal("Context timed out while waiting for a read buffer to be allocated.")
			} else if n != readBufSize {
				t.Fatalf("Unexpected request buffer size: got %d, want %d", n, readBufSize)
			}

			var res []byte
			select {
			case res = <-resCh:
			case <-ctx.Done():
				t.Fatal("Context timed out waiting for read to complete.")
			}
			if !bytes.Equal(res, data) {
				t.Errorf("Read data = %s; want %s", string(res), string(data))
			}
		})
	}
}

// trackingBufferPool wraps a mem.BufferPool and provides channels to track
// when buffers are requested and returned, useful for verifying allocation
// behavior in tests.
type trackingBufferPool struct {
	mem.BufferPool
	requestChan testutils.Channel
	putChan     testutils.Channel
}

func newTrackingPool(pool mem.BufferPool) *trackingBufferPool {
	return &trackingBufferPool{
		BufferPool:  pool,
		requestChan: *testutils.NewChannelWithSize(1),
		putChan:     *testutils.NewChannelWithSize(1),
	}
}

func (p *trackingBufferPool) Get(size int) *[]byte {
	p.requestChan.Replace(size)
	return p.BufferPool.Get(size)
}

func (p *trackingBufferPool) Put(b *[]byte) {
	p.putChan.Replace(len(*b))
	p.BufferPool.Put(b)
}

// Call Read to accumulate the text of a file
func reads(buf io.Reader, m int) string {
	var b [1000]byte
	nb := 0
	for {
		n, err := buf.Read(b[nb : nb+m])
		nb += n
		if err == io.EOF {
			break
		}
	}
	return string(b[0:nb])
}

type bufReader struct {
	name string
	fn   func(io.Reader) string
}

var bufreaders = []bufReader{
	{"1", func(b io.Reader) string { return reads(b, 1) }},
	{"2", func(b io.Reader) string { return reads(b, 2) }},
	{"3", func(b io.Reader) string { return reads(b, 3) }},
	{"4", func(b io.Reader) string { return reads(b, 4) }},
	{"5", func(b io.Reader) string { return reads(b, 5) }},
	{"7", func(b io.Reader) string { return reads(b, 7) }},
}

const minReadBufferSize = 16

var bufsizes = []int{
	0, minReadBufferSize, 23, 32, 46, 64, 93, 128, 1024, 4096,
}

func (s) TestBufReader(t *testing.T) {
	var texts [31]string
	str := ""
	all := ""
	for i := range len(texts) - 1 {
		texts[i] = str + "\n"
		all += texts[i]
		str += string(rune(i%26 + 'a'))
	}
	texts[len(texts)-1] = all

	for _, text := range texts {
		for _, bufreader := range bufreaders {
			for _, bufsize := range bufsizes {
				// We don't use t.Run() here to avoid excessive logging due to
				// the large number of subtests.
				read := readyreader.New(strings.NewReader(text))
				buf := readyreader.NewBuffered(read, bufsize, mem.DefaultBufferPool())
				s := bufreader.fn(buf)
				if s != text {
					t.Errorf("fn=%s bufsize=%d want=%q got=%q", bufreader.name, bufsize, text, s)
				}
			}
		}
	}
}

func (s) TestBufReader_ReadEmptyBuffer(t *testing.T) {
	rr := readyreader.New(new(bytes.Buffer))
	l := readyreader.NewBuffered(rr, minReadBufferSize, mem.DefaultBufferPool())
	b := make([]byte, 100)
	n, err := l.Read(b)
	if err != io.EOF {
		t.Errorf("expected EOF from Read, got %q %v", b[:n], err)
	}
}

type errorThenGoodReader struct {
	didErr bool
	nread  int
}

var errFake = errors.New("fake error")

func (r *errorThenGoodReader) Read(p []byte) (int, error) {
	r.nread++
	if !r.didErr {
		r.didErr = true
		return 0, errFake
	}
	return len(p), nil
}

func (s) TestBufReader_ClearError(t *testing.T) {
	r := &errorThenGoodReader{}
	b := readyreader.NewBuffered(readyreader.New(r), minReadBufferSize, mem.DefaultBufferPool())
	buf := make([]byte, 1)
	if _, err := b.Read(nil); err != nil {
		t.Fatalf("1st nil Read = %v; want nil", err)
	}
	if _, err := b.Read(buf); err != errFake {
		t.Fatalf("1st Read = %v; want errFake", err)
	}
	if _, err := b.Read(nil); err != nil {
		t.Fatalf("2nd nil Read = %v; want nil", err)
	}
	if _, err := b.Read(buf); err != nil {
		t.Fatalf("3rd Read with buffer = %v; want nil", err)
	}
	if r.nread != 2 {
		t.Errorf("num reads = %d; want 2", r.nread)
	}
}

type emptyThenNonEmptyReader struct {
	r io.Reader
	n int
}

func (r *emptyThenNonEmptyReader) Read(p []byte) (int, error) {
	if r.n <= 0 {
		return r.r.Read(p)
	}
	r.n--
	return 0, nil
}

func (s) TestBufReader_ReadZero(t *testing.T) {
	for _, size := range []int{100, 2} {
		t.Run(fmt.Sprintf("bufsize=%d", size), func(t *testing.T) {
			r := io.MultiReader(strings.NewReader("abc"), &emptyThenNonEmptyReader{r: strings.NewReader("def"), n: 1})
			br := readyreader.NewBuffered(readyreader.New(r), size, mem.DefaultBufferPool())
			want := func(s string, wantErr error) {
				p := make([]byte, 50)
				n, err := br.Read(p)
				if err != wantErr || n != len(s) || string(p[:n]) != s {
					t.Fatalf("read(%d) = %q, %v, want %q, %v", len(p), string(p[:n]), err, s, wantErr)
				}
				t.Logf("read(%d) = %q, %v", len(p), string(p[:n]), err)
			}
			want("abc", nil)
			want("", nil)
			want("def", nil)
			want("", io.EOF)
		})
	}
}
