package server

import (
	"bytes"
	"fmt"
	"io"
	"math/rand"
	"sync"
	"testing"
	"time"

	"github.com/golang/protobuf/proto"
	"go-micro.dev/v5/codec/json"
	protoCodec "go-micro.dev/v5/codec/proto"
)

// protoStruct implements proto.Message.
type protoStruct struct {
	Payload string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
}

func (m *protoStruct) Reset()         { *m = protoStruct{} }
func (m *protoStruct) String() string { return proto.CompactTextString(m) }
func (*protoStruct) ProtoMessage()    {}

// safeBuffer throws away everything and wont Read data back.
type safeBuffer struct {
	sync.RWMutex
	buf []byte
	off int
}

func (b *safeBuffer) Write(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, nil
	}
	// Cannot retain p, so we must copy it:
	p2 := make([]byte, len(p))
	copy(p2, p)
	b.Lock()
	b.buf = append(b.buf, p2...)
	b.Unlock()
	return len(p2), nil
}

func (b *safeBuffer) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, nil
	}
	b.RLock()
	n = copy(p, b.buf[b.off:])
	b.RUnlock()
	if n == 0 {
		return 0, io.EOF
	}
	b.off += n
	return n, nil
}

func (b *safeBuffer) Close() error {
	return nil
}

func TestRPCStream_Sequence(t *testing.T) {
	buffer := new(bytes.Buffer)
	rwc := readWriteCloser{
		rbuf: buffer,
		wbuf: buffer,
	}
	codec := json.NewCodec(&rwc)
	streamServer := rpcStream{
		codec: codec,
		request: &rpcRequest{
			codec: codec,
		},
	}

	// Check if sequence is correct
	for i := 0; i < 1000; i++ {
		if err := streamServer.Send(fmt.Sprintf(`{"test":"value %d"}`, i)); err != nil {
			t.Errorf("Unexpected Send error: %s", err)
		}
	}

	for i := 0; i < 1000; i++ {
		var msg string
		if err := streamServer.Recv(&msg); err != nil {
			t.Errorf("Unexpected Recv error: %s", err)
		}
		if msg != fmt.Sprintf(`{"test":"value %d"}`, i) {
			t.Errorf("Unexpected msg: %s", msg)
		}
	}
}

func TestRPCStream_Concurrency(t *testing.T) {
	buffer := new(safeBuffer)
	codec := protoCodec.NewCodec(buffer)
	streamServer := rpcStream{
		codec: codec,
		request: &rpcRequest{
			codec: codec,
		},
	}

	var wg sync.WaitGroup
	// Check if race conditions happen
	for i := 0; i < 10; i++ {
		wg.Add(2)

		go func() {
			for i := 0; i < 50; i++ {
				msg := protoStruct{Payload: "test"}
				<-time.After(time.Duration(rand.Intn(50)) * time.Millisecond)
				if err := streamServer.Send(msg); err != nil {
					t.Errorf("Unexpected Send error: %s", err)
				}
			}
			wg.Done()
		}()

		go func() {
			for i := 0; i < 50; i++ {
				<-time.After(time.Duration(rand.Intn(50)) * time.Millisecond)
				if err := streamServer.Recv(&protoStruct{}); err != nil {
					t.Errorf("Unexpected Recv error: %s", err)
				}
			}
			wg.Done()
		}()
	}
	wg.Wait()
}
