// Copyright 2026 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 nettest_test

import (
	"bytes"
	"errors"
	"internal/nettest"
	"io"
	"net"
	"os"
	"testing"
	"testing/synctest"
	"time"
)

func TestConnReadWrite(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		cliConn, srvConn := nettest.NewConnPair()

		cliData := []byte("hello")
		srvData := []byte("HELLO")
		if n, err := cliConn.Write(cliData); n != len(cliData) || err != nil {
			t.Fatalf("cliConn.Write(%q) = %v, %v; want %v, nil", cliData, n, err, len(cliData))
		}
		if err := cliConn.CloseWrite(); err != nil {
			t.Fatalf("cliConn.CloseWrite() = %v, want nil", err)
		}
		if n, err := srvConn.Write(srvData); n != len(srvData) || err != nil {
			t.Fatalf("srvConn.Write(%q) = %v, %v; want %v, nil", srvData, n, err, len(srvData))
		}
		if err := srvConn.CloseWrite(); err != nil {
			t.Fatalf("cliConn.CloseWrite() = %v, want nil", err)
		}
		gotCli, err := io.ReadAll(cliConn)
		if !bytes.Equal(gotCli, srvData) || err != nil {
			t.Fatalf("io.ReadAll(cliConn) = %q, %v; want %v, nil", gotCli, err, srvData)
		}
		gotSrv, err := io.ReadAll(srvConn)
		if !bytes.Equal(gotSrv, cliData) || err != nil {
			t.Fatalf("io.ReadAll(srvConn) = %q, %v; want %v, nil", gotSrv, err, cliData)
		}
	})
}

func TestConnZeroBuffer(t *testing.T) {
	// Exercise the case where one side of the conn is blocked writing and the
	// other side is blocked reading.
	// This can only happen when the read buffer has been set to 0, blocking all writes.
	synctest.Test(t, func(t *testing.T) {
		rconn, wconn := nettest.NewConnPair()
		rconn.SetReadBufferSize(0)
		var readDone, writeDone bool
		go func() {
			rconn.Read(make([]byte, 100))
			readDone = true
		}()
		go func() {
			wconn.Write([]byte("a"))
			writeDone = true
		}()
		synctest.Wait()
		if readDone || writeDone {
			t.Errorf("before unblocking: readDone=%v, writeDone=%v; want false", readDone, writeDone)
		}
		wconn.Close()
		synctest.Wait()
		if !readDone || !writeDone {
			t.Errorf("after unblocking: readDone=%v, writeDone=%v; want true", readDone, writeDone)
		}
	})
}

func TestConnPartialWrite(t *testing.T) {
	// A blocking write to a conn successfully writes some, but not all data.
	synctest.Test(t, func(t *testing.T) {
		const readSize = 5
		data := []byte("0123456789")
		rconn, wconn := nettest.NewConnPair()
		rconn.SetReadBufferSize(1)
		go func() {
			got := make([]byte, readSize)
			if n, err := io.ReadFull(rconn, got); n != readSize || err != nil {
				t.Errorf("io.ReadFull() = %v, %v; want %v, nil", n, err, readSize)
			}
			if want := data[:readSize]; !bytes.Equal(got, want) {
				t.Errorf("read %q, want %q", got, want)
			}
			synctest.Wait()
			rconn.Close()
		}()
		n, err := wconn.Write(data)
		if n != readSize+1 || err == nil {
			t.Errorf("Write() = %v, %v; want %v, error", n, err, readSize+1)
		}
	})
}

func TestConnReadDeadline(t *testing.T) {
	for _, unblock := range []struct {
		name string
		f    func(*nettest.Conn)
	}{{
		name: "Write",
		f: func(c *nettest.Conn) {
			c.Write([]byte("x"))
		},
	}, {
		name: "Close",
		f: func(c *nettest.Conn) {
			c.Close()
		},
	}, {
		name: "CloseWrite",
		f: func(c *nettest.Conn) {
			c.CloseWrite()
		},
	}} {
		for _, setDeadline := range []struct {
			name string
			f    func(*nettest.Conn, time.Time) error
		}{{
			name: "SetDeadline",
			f:    (*nettest.Conn).SetDeadline,
		}, {
			name: "SetReadDeadline",
			f:    (*nettest.Conn).SetReadDeadline,
		}} {
			t.Run(unblock.name+"/"+setDeadline.name, func(t *testing.T) {
				testDeadline(t, func() deadlineTest {
					rconn, wconn := nettest.NewConnPair()
					return deadlineTest{
						what: "Read()",
						block: func() error {
							_, err := rconn.Read(make([]byte, 1))
							return err
						},
						unblock: func() {
							unblock.f(wconn)
						},
						setDeadline: func(d time.Duration) {
							setDeadline.f(rconn, time.Now().Add(d))
						},
					}
				})
			})
		}
	}
}

func TestConnWriteDeadline(t *testing.T) {
	for _, unblock := range []struct {
		name string
		f    func(*nettest.Conn)
	}{{
		name: "Read",
		f: func(c *nettest.Conn) {
			io.Copy(io.Discard, c)
		},
	}, {
		name: "Close",
		f: func(c *nettest.Conn) {
			c.Close()
		},
	}, {
		name: "CloseRead",
		f: func(c *nettest.Conn) {
			c.CloseRead()
		},
	}} {
		for _, setDeadline := range []struct {
			name string
			f    func(*nettest.Conn, time.Time) error
		}{{
			name: "SetDeadline",
			f:    (*nettest.Conn).SetDeadline,
		}, {
			name: "SetWriteDeadline",
			f:    (*nettest.Conn).SetWriteDeadline,
		}} {
			t.Run(unblock.name+"/"+setDeadline.name, func(t *testing.T) {
				testDeadline(t, func() deadlineTest {
					rconn, wconn := nettest.NewConnPair()
					rconn.SetReadBufferSize(1)
					return deadlineTest{
						what: "Write()",
						block: func() error {
							_, err := wconn.Write([]byte("1234"))
							wconn.Close()
							return err
						},
						unblock: func() {
							go unblock.f(rconn)
						},
						setDeadline: func(d time.Duration) {
							setDeadline.f(wconn, time.Now().Add(d))
						},
					}
				})
			})
		}
	}
}

func TestConnCanRead(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		rconn, wconn := nettest.NewConnPair()
		if got, want := rconn.CanRead(), false; got != want {
			t.Fatalf("before writing data: rconn.CanRead() = %v, want %v", got, want)
		}
		wconn.Write([]byte("a"))
		if got, want := rconn.CanRead(), true; got != want {
			t.Fatalf("after writing data: rconn.CanRead() = %v, want %v", got, want)
		}
		rconn.Read(make([]byte, 1))
		if got, want := rconn.CanRead(), false; got != want {
			t.Fatalf("after reading data: rconn.CanRead() = %v, want %v", got, want)
		}
		wconn.Close()
		if got, want := rconn.CanRead(), true; got != want {
			t.Fatalf("after closing: rconn.CanRead() = %v, want %v", got, want)
		}
	})
}

func TestConnIsClosed(t *testing.T) {
	for _, test := range []struct {
		name string
		f    func() *nettest.Conn
		want bool
	}{{
		name: "unclosed",
		f: func() *nettest.Conn {
			conn, _ := nettest.NewConnPair()
			return conn
		},
		want: false,
	}, {
		name: "closed",
		f: func() *nettest.Conn {
			conn, _ := nettest.NewConnPair()
			conn.Close()
			return conn
		},
		want: true,
	}, {
		name: "read-closed",
		f: func() *nettest.Conn {
			conn, _ := nettest.NewConnPair()
			conn.CloseRead()
			return conn
		},
		want: false,
	}, {
		name: "write-closed",
		f: func() *nettest.Conn {
			conn, _ := nettest.NewConnPair()
			conn.CloseWrite()
			return conn
		},
		want: false,
	}, {
		name: "read-write-closed",
		f: func() *nettest.Conn {
			conn, _ := nettest.NewConnPair()
			conn.CloseRead()
			conn.CloseWrite()
			return conn
		},
		want: true,
	}} {
		synctestSubtest(t, test.name, func(t *testing.T) {
			conn := test.f()
			if got, want := conn.IsClosed(), test.want; got != want {
				t.Fatalf("conn.IsClosed() = %v, want %v", got, want)
			}
			if got, want := conn.Peer().IsClosed(), false; got != want {
				t.Fatalf("conn.Peer().IsClosed() = %v, want %v", got, want)
			}
		})
	}
}

var anyError = errors.New("any") // anyError is passed to isOpError to match any error

func isOpError(err, want error) bool {
	oe, ok := err.(*net.OpError)
	return ok && (oe.Err == want || want == anyError)
}

func wantConnReadBytes(t *testing.T, c *nettest.Conn, want []byte) {
	t.Helper()
	got := make([]byte, len(want))
	n, err := io.ReadFull(c, got)
	if n < len(want) || err != nil {
		t.Fatalf("io.ReadFull = %v, %v; want %v, nil", n, err, len(want))
	}

	if !bytes.Equal(got, want) {
		t.Fatalf("io.ReadFull read %q, want %q", got, want)
	}
}

func wantConnReadErr(t *testing.T, c *nettest.Conn, want error) {
	t.Helper()
	n, err := c.Read(make([]byte, 1))
	if want == io.EOF {
		if n != 0 || err != io.EOF {
			t.Fatalf("c.Read() = %v, %v; want 0, io.EOF", n, err)
		}
	} else {
		if n != 0 || !isOpError(err, want) {
			t.Fatalf("c.Read() = %v, %v; want 0, OpError{Err: %q}", n, err, want)
		}
	}
}

func wantConnReadBlocked(t *testing.T, c *nettest.Conn) {
	done := false
	go func() {
		n, err := c.Read(make([]byte, 1))
		if n != 0 || !errors.Is(err, os.ErrDeadlineExceeded) {
			t.Errorf("c.Read() = %v, %v; want 0, ErrDeadlineExceeded", n, err)
		}
		done = true
	}()
	synctest.Wait()
	if done {
		t.Fatalf("Read unexpectedly returned before setting deadline")
	}
	c.SetReadDeadline(time.Now().Add(-1 * time.Second))
	synctest.Wait()
	c.SetReadDeadline(time.Time{})
	if !done {
		t.Fatalf("Read unexpectedly did not return after setting deadline")
	}
}

func TestConnSetReadError(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		wantErr := errors.New("error")
		rconn, wconn := nettest.NewConnPair()
		rconn.SetReadError(wantErr)

		// Consume buffer before returning error.
		wconn.Write([]byte("one"))
		wantConnReadBytes(t, rconn, []byte("one"))
		wantConnReadErr(t, rconn, wantErr)

		// Write more to the buffer, suppressing error until buffer drains again.
		wconn.Write([]byte("two"))
		wantConnReadBytes(t, rconn, []byte("two"))
		wantConnReadErr(t, rconn, wantErr)

		// Error may be cleared.
		rconn.SetReadError(nil)
		wantConnReadBlocked(t, rconn)

		// Close overrides read error.
		rconn.SetReadError(wantErr)
		wconn.Write([]byte("three"))
		wconn.Close()
		wantConnReadBytes(t, rconn, []byte("three"))
		wantConnReadErr(t, rconn, io.EOF)

		// Setting another read error does not override Close.
		rconn.SetReadError(nil)
		wantConnReadErr(t, rconn, io.EOF)
		rconn.SetReadError(wantErr)
		wantConnReadErr(t, rconn, io.EOF)

		// ErrClosed takes precedence over read error.
		rconn.Close()
		wantConnReadErr(t, rconn, net.ErrClosed)
	})
}

func wantConnWriteBytes(t *testing.T, c *nettest.Conn, b []byte) {
	t.Helper()
	if n, err := c.Write(b); n != len(b) || err != nil {
		t.Fatalf("c.Write() = %v, %v; want %v, nil", n, err, len(b))
	}
}

func wantConnWriteErr(t *testing.T, c *nettest.Conn, want error) {
	t.Helper()
	n, err := c.Write(make([]byte, 1))
	if n != 0 || !isOpError(err, want) {
		t.Fatalf("c.Write() = %v, %v; want 0, OpError{Err: %q}", n, err, want)
	}
}

func TestConnSetWriteError(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		wantErr := errors.New("error")
		rconn, wconn := nettest.NewConnPair()
		wconn.SetWriteError(wantErr)

		// Error blocks writes.
		wantConnWriteErr(t, wconn, wantErr)
		wantConnReadBlocked(t, rconn)

		// Error may be cleared.
		wconn.SetWriteError(nil)
		wantConnWriteBytes(t, wconn, []byte("one"))

		// Restoring error does not prevent reading buffered data.
		wconn.SetWriteError(wantErr)
		wantConnWriteErr(t, wconn, wantErr)
		wantConnReadBytes(t, rconn, []byte("one"))

		// Error does not interfere with closing the conn.
		wconn.Close()
		wantConnReadErr(t, rconn, io.EOF)
	})
}

func TestConnSetCloseError(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		wantErr := errors.New("error")
		rconn, wconn := nettest.NewConnPair()

		wconn.SetCloseError(wantErr)
		if _, err := wconn.Write([]byte("one")); err != nil {
			t.Fatalf("wconn.Write = %v, want success", err)
		}
		if err := wconn.Close(); !isOpError(err, wantErr) {
			t.Fatalf("wconn.Close = %v, want OpError{Err: %v}", err, wantErr)
		}
		if err := wconn.Close(); !isOpError(err, net.ErrClosed) {
			t.Fatalf("wconn.Close = %v, want OpError{Err: net.ErrClosed}", err)
		}
		wantConnReadBytes(t, rconn, []byte("one"))
		wantConnReadErr(t, rconn, io.EOF)
	})
}

func TestConnCloseReadWriteError(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		conn, _ := nettest.NewConnPair()
		conn.SetCloseError(errors.New("error"))
		if err := conn.CloseRead(); err != nil {
			t.Fatalf("conn.CloseRead = %v, want nil", err)
		}
		if err := conn.CloseWrite(); err != nil {
			t.Fatalf("conn.CloseRead = %v, want nil", err)
		}
	})
}
