/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 remote

import (
	"bytes"
	"context"
	"github.com/apache/rocketmq-client-go/v2/errors"
	"math/rand"
	"net"
	"reflect"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestNewResponseFuture(t *testing.T) {
	future := NewResponseFuture(context.Background(), 10, nil)
	if future.Opaque != 10 {
		t.Errorf("wrong ResponseFuture's opaque. want=%d, got=%d", 10, future.Opaque)
	}
	if future.Err != nil {
		t.Errorf("wrong RespnseFuture's Err. want=<nil>, got=%v", future.Err)
	}
	if future.callback != nil {
		t.Errorf("wrong ResponseFuture's callback. want=<nil>, got!=<nil>")
	}
	if future.Done == nil {
		t.Errorf("wrong ResponseFuture's done. want=<channel>, got=<nil>")
	}
}

func TestResponseFutureTimeout(t *testing.T) {
	callback := func(r *ResponseFuture) {
		if r.ResponseCommand.Remark == "" {
			r.ResponseCommand.Remark = "Hello RocketMQ."
		} else {
			r.ResponseCommand.Remark = r.ResponseCommand.Remark + "Go Client"
		}
	}
	future := NewResponseFuture(context.Background(), 10, callback)
	future.ResponseCommand = NewRemotingCommand(200,
		nil, nil)

	var wg sync.WaitGroup
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			future.executeInvokeCallback()
			wg.Done()
		}()
	}
	wg.Wait()
	if future.ResponseCommand.Remark != "Hello RocketMQ." {
		t.Errorf("wrong ResponseFuture.ResponseCommand.Remark. want=%s, got=%s",
			"Hello RocketMQ.", future.ResponseCommand.Remark)
	}

}

func TestResponseFutureWaitResponse(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(1000))
	defer cancel()
	future := NewResponseFuture(ctx, 10, nil)
	if _, err := future.waitResponse(); err != errors.ErrRequestTimeout {
		t.Errorf("wrong ResponseFuture waitResponse. want=%v, got=%v",
			errors.ErrRequestTimeout, err)
	}
	future = NewResponseFuture(context.Background(), 10, nil)
	responseError := errors.ErrResponse
	go func() {
		time.Sleep(100 * time.Millisecond)
		future.Err = responseError
		future.Done <- true
	}()
	if _, err := future.waitResponse(); err != responseError {
		t.Errorf("wrong ResponseFuture waitResponse. want=%v. got=%v",
			responseError, err)
	}
	future = NewResponseFuture(context.Background(), 10, nil)
	responseRemotingCommand := NewRemotingCommand(202, nil, nil)
	go func() {
		time.Sleep(100 * time.Millisecond)
		future.ResponseCommand = responseRemotingCommand
		future.Done <- true
	}()
	if r, err := future.waitResponse(); err != nil {
		t.Errorf("wrong ResponseFuture waitResponse error: %v", err)
	} else {
		if r != responseRemotingCommand {
			t.Errorf("wrong ResponseFuture waitResposne result. want=%v, got=%v",
				responseRemotingCommand, r)
		}
	}
}

func TestCreateScanner(t *testing.T) {
	r := randomNewRemotingCommand()
	content, err := encode(r)
	if err != nil {
		t.Fatalf("failed to encode RemotingCommand. %s", err)
	}
	client := NewRemotingClient(nil)
	reader := bytes.NewReader(content)
	scanner := client.createScanner(reader)
	for scanner.Scan() {
		rcr, err := decode(scanner.Bytes())
		if err != nil {
			t.Fatalf("failedd to decode RemotingCommand from scanner")
		}
		if r.Code != rcr.Code {
			t.Fatalf("wrong Code. want=%d, got=%d", r.Code, rcr.Code)
		}
		if r.Version != rcr.Version {
			t.Fatalf("wrong Version. want=%d, got=%d", r.Version, rcr.Version)
		}
		if r.Opaque != rcr.Opaque {
			t.Fatalf("wrong opaque. want=%d, got=%d", r.Opaque, rcr.Opaque)
		}
		if r.Flag != rcr.Flag {
			t.Fatalf("wrong flag. want=%d, got=%d", r.Opaque, rcr.Opaque)
		}
		if !reflect.DeepEqual(r.ExtFields, rcr.ExtFields) {
			t.Fatalf("wrong extFields. want=%v, got=%v", r.ExtFields, rcr.ExtFields)
		}
	}
}

func TestInvokeSync(t *testing.T) {
	addr := ":3004"

	clientSendRemtingCommand := NewRemotingCommand(10, nil, []byte("Hello RocketMQ"))
	serverSendRemotingCommand := NewRemotingCommand(20, nil, []byte("Welcome native"))
	serverSendRemotingCommand.Opaque = clientSendRemtingCommand.Opaque
	serverSendRemotingCommand.Flag = ResponseType
	var wg sync.WaitGroup
	wg.Add(1)
	client := NewRemotingClient(nil)

	var clientSend sync.WaitGroup // blocking client send message until the server listen success.
	clientSend.Add(1)

	go func() {
		clientSend.Wait()
		receiveCommand, err := client.InvokeSync(context.Background(), addr,
			clientSendRemtingCommand)
		if err != nil {
			t.Fatalf("failed to invoke synchronous. %s", err)
		} else {
			assert.Equal(t, len(receiveCommand.ExtFields), 0)
			assert.Equal(t, len(serverSendRemotingCommand.ExtFields), 0)
			// in order to avoid the difference of ExtFields between the receiveCommand and serverSendRemotingCommand
			// the ExtFields in receiveCommand is map[string]string(nil), but serverSendRemotingCommand is map[string]string{}
			receiveCommand.ExtFields = nil
			serverSendRemotingCommand.ExtFields = nil
			assert.Equal(t, receiveCommand, serverSendRemotingCommand, "remotingCommand prased in client is different from server.")
		}
		wg.Done()
	}()

	l, err := net.Listen("tcp", addr)
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()
	clientSend.Done()
	for {
		conn, err := l.Accept()
		if err != nil {
			return
		}
		defer conn.Close()
		scanner := client.createScanner(conn)
		for scanner.Scan() {
			receivedRemotingCommand, err := decode(scanner.Bytes())
			if err != nil {
				t.Errorf("failed to decode RemotingCommnad. %s", err)
			}
			if clientSendRemtingCommand.Code != receivedRemotingCommand.Code {
				t.Errorf("wrong code. want=%d, got=%d", receivedRemotingCommand.Code,
					clientSendRemtingCommand.Code)
			}
			body, err := encode(serverSendRemotingCommand)
			if err != nil {
				t.Fatalf("failed to encode RemotingCommand")
			}
			_, err = conn.Write(body)
			if err != nil {
				t.Fatalf("failed to write body to conneciton.")
			}
			goto done
		}
	}
done:
	wg.Wait()
}

func TestInvokeAsync(t *testing.T) {
	addr := ":3006"
	var wg sync.WaitGroup
	cnt := 50
	wg.Add(cnt)
	client := NewRemotingClient(nil)
	for i := 0; i < cnt; i++ {
		go func(index int) {
			time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
			t.Logf("[Send: %d] asychronous message", index)
			sendRemotingCommand := randomNewRemotingCommand()
			err := client.InvokeAsync(context.Background(), addr, sendRemotingCommand, func(r *ResponseFuture) {
				t.Logf("[Receive: %d] asychronous message response", index)
				if string(sendRemotingCommand.Body) != string(r.ResponseCommand.Body) {
					t.Errorf("wrong response message. want=%s, got=%s", string(sendRemotingCommand.Body),
						string(r.ResponseCommand.Body))
				}
				wg.Done()
			})
			if err != nil {
				t.Errorf("failed to invokeAsync. %s", err)
			}

		}(i)
	}
	l, err := net.Listen("tcp", addr)
	if err != nil {
		t.Fatalf("failed to create tcp network. %s", err)
	}
	defer l.Close()
	count := 0
	for {
		conn, err := l.Accept()
		if err != nil {
			t.Fatalf("failed to create connection. %s", err)
		}
		defer conn.Close()
		scanner := client.createScanner(conn)
		for scanner.Scan() {
			t.Log("receive request")
			r, err := decode(scanner.Bytes())
			if err != nil {
				t.Errorf("failed to decode RemotingCommand %s", err)
			}
			r.markResponseType()
			body, _ := encode(r)
			_, err = conn.Write(body)
			if err != nil {
				t.Fatalf("failed to send response %s", err)
			}
			count++
			if count >= cnt {
				goto done
			}
		}
	}
done:

	wg.Wait()
}

func TestInvokeAsyncTimeout(t *testing.T) {
	addr := ":3002"

	clientSendRemtingCommand := NewRemotingCommand(10, nil, []byte("Hello RocketMQ"))
	serverSendRemotingCommand := NewRemotingCommand(20, nil, []byte("Welcome native"))
	serverSendRemotingCommand.Opaque = clientSendRemtingCommand.Opaque
	serverSendRemotingCommand.Flag = ResponseType

	var wg sync.WaitGroup
	wg.Add(1)
	client := NewRemotingClient(nil)

	var clientSend sync.WaitGroup // blocking client send message until the server listen success.
	clientSend.Add(1)
	go func() {
		clientSend.Wait()
		ctx, cancel := context.WithTimeout(context.Background(), time.Duration(10*time.Second))
		defer cancel()
		err := client.InvokeAsync(ctx, addr, clientSendRemtingCommand,
			func(r *ResponseFuture) {
				assert.NotNil(t, r.Err)
				assert.Equal(t, errors.ErrRequestTimeout, r.Err)
				wg.Done()
			})
		assert.Nil(t, err, "failed to invokeSync.")
	}()

	l, err := net.Listen("tcp", addr)
	assert.Nil(t, err)
	defer l.Close()
	clientSend.Done()

	for {
		conn, err := l.Accept()
		assert.Nil(t, err)
		defer conn.Close()

		scanner := client.createScanner(conn)
		for scanner.Scan() {
			t.Logf("receive request.")
			_, err := decode(scanner.Bytes())
			assert.Nil(t, err, "failed to decode RemotingCommnad.")

			time.Sleep(5 * time.Second) // force client timeout
			goto done
		}
	}
done:
	wg.Wait()
}

func TestInvokeOneWay(t *testing.T) {
	addr := ":3008"
	clientSendRemtingCommand := NewRemotingCommand(10, nil, []byte("Hello RocketMQ"))

	var wg sync.WaitGroup
	wg.Add(1)
	client := NewRemotingClient(nil)

	var clientSend sync.WaitGroup // blocking client send message until the server listen success.
	clientSend.Add(1)
	go func() {
		clientSend.Wait()
		err := client.InvokeOneWay(context.Background(), addr, clientSendRemtingCommand)
		if err != nil {
			t.Fatalf("failed to invoke synchronous. %s", err)
		}
		wg.Done()
	}()

	l, err := net.Listen("tcp", addr)
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()
	clientSend.Done()
	for {
		conn, err := l.Accept()
		if err != nil {
			return
		}
		defer conn.Close()
		scanner := client.createScanner(conn)
		for scanner.Scan() {
			receivedRemotingCommand, err := decode(scanner.Bytes())
			if err != nil {
				t.Errorf("failed to decode RemotingCommnad. %s", err)
			}
			if clientSendRemtingCommand.Code != receivedRemotingCommand.Code {
				t.Errorf("wrong code. want=%d, got=%d", receivedRemotingCommand.Code,
					clientSendRemtingCommand.Code)
			}
			goto done
		}
	}
done:
	wg.Wait()
}
