/*
 * Copyright 2021 CloudWeGo 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 remotecli

import (
	"context"
	"errors"
	"net"
	"testing"

	"github.com/golang/mock/gomock"

	mocksnetpoll "github.com/cloudwego/kitex/internal/mocks/netpoll"
	mocksremote "github.com/cloudwego/kitex/internal/mocks/remote"
	"github.com/cloudwego/kitex/internal/test"
	"github.com/cloudwego/kitex/pkg/discovery"
	"github.com/cloudwego/kitex/pkg/kerrors"
	"github.com/cloudwego/kitex/pkg/remote"
	"github.com/cloudwego/kitex/pkg/remote/connpool"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
	"github.com/cloudwego/kitex/pkg/rpcinfo/remoteinfo"
	"github.com/cloudwego/kitex/pkg/transmeta"
	"github.com/cloudwego/kitex/pkg/utils"
)

// TestNewClientNoAddr test new client return err because no addr
func TestNewClientNoAddr(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	// 1. prepare mock data
	ctx := context.Background()

	to := remoteinfo.NewRemoteInfo(&rpcinfo.EndpointBasicInfo{}, "")
	conf := rpcinfo.NewRPCConfig()
	ri := rpcinfo.NewRPCInfo(nil, to, rpcinfo.NewInvocation("", ""), conf, rpcinfo.NewRPCStats())

	hdlr := mocksremote.NewMockClientTransHandler(ctrl)
	connPool := connpool.NewLongPool("destService", poolCfg)
	opt := &remote.ClientOption{
		ConnPool: connPool,
		Dialer:   mocksremote.NewMockDialer(ctrl),
	}

	// 2. test
	_, err := NewClient(ctx, ri, hdlr, opt)
	test.Assert(t, err != nil, err)
	test.Assert(t, errors.Is(err, kerrors.ErrGetConnection))
}

// TestNewClient test new a client
func TestNewClient(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	addr := utils.NewNetAddr("tcp", "to")
	ri := newMockRPCInfo(addr)
	ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri)

	hdlr := mocksremote.NewMockClientTransHandler(ctrl)

	conn := mocksnetpoll.NewMockConnection(ctrl)
	conn.EXPECT().RemoteAddr().Return(addr).AnyTimes()

	dialer := mocksremote.NewMockDialer(ctrl)
	dialer.EXPECT().DialTimeout(addr.Network(), addr.String(), gomock.Any()).Return(conn, nil).Times(1)

	connPool := connpool.NewLongPool("destService", poolCfg)

	opt := &remote.ClientOption{
		ConnPool: connPool,
		Dialer:   dialer,
		Option: remote.Option{
			StreamingMetaHandlers: []remote.StreamingMetaHandler{
				transmeta.ClientHTTP2Handler,
			},
		},
	}

	cli, err := NewClient(ctx, ri, hdlr, opt)
	test.Assert(t, err == nil, err)
	test.Assert(t, cli != nil, cli)
}

func newMockRPCInfo(addr net.Addr) rpcinfo.RPCInfo {
	from := rpcinfo.NewEndpointInfo("from", "method", nil, nil)
	to := remoteinfo.NewRemoteInfo(&rpcinfo.EndpointBasicInfo{}, "")
	to.SetInstance(discovery.NewInstance(addr.Network(), addr.String(), discovery.DefaultWeight, nil))
	conf := rpcinfo.NewRPCConfig()
	ri := rpcinfo.NewRPCInfo(from, to, rpcinfo.NewInvocation("", ""), conf, rpcinfo.NewRPCStats())

	return ri
}

func newMockOption(ctrl *gomock.Controller, addr net.Addr) *remote.ClientOption {
	conn := mocksnetpoll.NewMockConnection(ctrl)
	conn.EXPECT().RemoteAddr().Return(addr).AnyTimes()
	conn.EXPECT().IsActive().Return(true).AnyTimes()
	conn.EXPECT().Close().Return(nil).AnyTimes()

	dialer := mocksremote.NewMockDialer(ctrl)
	dialer.EXPECT().DialTimeout(gomock.Any(), gomock.Any(), gomock.Any()).Return(conn, nil).AnyTimes()

	connPool := connpool.NewLongPool("destService", poolCfg)

	opt := &remote.ClientOption{
		ConnPool: connPool,
		Dialer:   dialer,
	}

	return opt
}

// TestSend test send msg
func TestSend(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	// 1. prepare mock data
	ctx := context.Background()
	addr := utils.NewNetAddr("tcp", "to")
	ri := newMockRPCInfo(addr)

	var sendMsg remote.Message
	hdlr := mocksremote.NewMockClientTransHandler(ctrl)
	hdlr.EXPECT().Write(gomock.Any(), gomock.Any(), sendMsg).Return(ctx, nil).MinTimes(1)

	opt := newMockOption(ctrl, addr)

	cli, err := NewClient(ctx, ri, hdlr, opt)
	test.Assert(t, err == nil, err)
	test.Assert(t, cli != nil, cli)

	// 2. test
	err = cli.Send(ctx, ri, sendMsg)
	test.Assert(t, err == nil, err)
}

// TestSendErr test send return err because write fail
func TestSendErr(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	// 1. prepare mock data
	addr := utils.NewNetAddr("tcp", "to")
	ri := newMockRPCInfo(addr)
	ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri)

	var sendMsg remote.Message
	errMsg := "mock test send err"

	hdlr := mocksremote.NewMockClientTransHandler(ctrl)
	hdlr.EXPECT().Write(gomock.Any(), gomock.Any(), sendMsg).Return(ctx, errors.New(errMsg)).MinTimes(1)

	opt := newMockOption(ctrl, addr)

	conn := mocksnetpoll.NewMockConnection(ctrl)
	conn.EXPECT().RemoteAddr().Return(addr).AnyTimes()
	conn.EXPECT().Close().Return(nil).Times(1)

	dialer := mocksremote.NewMockDialer(ctrl)
	dialer.EXPECT().DialTimeout(addr.Network(), addr.String(), gomock.Any()).Return(conn, nil).Times(1)
	opt.Dialer = dialer

	// 2. test
	cli, err := NewClient(ctx, ri, hdlr, opt)
	test.Assert(t, err == nil, err)
	test.Assert(t, cli != nil, cli)

	err = cli.Send(ctx, ri, sendMsg)
	test.Assert(t, err != nil, err)
	test.Assert(t, err.Error() == errMsg)
}

// TestRecv test recv msg
func TestRecv(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	ctx := context.Background()

	addr := utils.NewNetAddr("tcp", "to")
	ri := newMockRPCInfo(addr)

	var resp interface{}
	msg := remote.NewMessage(resp, ri, remote.Call, remote.Client)

	hdlr := mocksremote.NewMockClientTransHandler(ctrl)
	hdlr.EXPECT().Read(gomock.Any(), gomock.Any(), msg).Return(ctx, nil).MinTimes(1)
	hdlr.EXPECT().OnMessage(gomock.Any(), gomock.Any(), gomock.Any()).Return(ctx, nil).AnyTimes()

	opt := newMockOption(ctrl, addr)

	cli, err := NewClient(ctx, ri, hdlr, opt)
	test.Assert(t, err == nil, err)
	test.Assert(t, cli != nil, cli)

	err = cli.Recv(ctx, ri, msg)
	test.Assert(t, err == nil, err)

	readErr := errors.New("read failed")
	onMessageErr := errors.New("on message failed")

	hdlr = mocksremote.NewMockClientTransHandler(ctrl)
	hdlr.EXPECT().Read(gomock.Any(), gomock.Any(), gomock.Any()).Return(ctx, readErr).AnyTimes()
	hdlr.EXPECT().OnMessage(gomock.Any(), gomock.Any(), gomock.Any()).Return(ctx, onMessageErr).AnyTimes()

	cli, err = NewClient(ctx, ri, hdlr, opt)
	test.Assert(t, err == nil, err)
	test.Assert(t, cli != nil, cli)

	err = cli.Recv(ctx, ri, msg)
	test.Assert(t, err == readErr, err)
}

// TestRecvOneWay test recv own way msg
func TestRecvOneWay(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	ctx := context.Background()

	addr := utils.NewNetAddr("tcp", "to")
	ri := newMockRPCInfo(addr)

	hdlr := mocksremote.NewMockClientTransHandler(ctrl)

	opt := newMockOption(ctrl, addr)
	opt.ConnPool = nil

	conn := mocksnetpoll.NewMockConnection(ctrl)
	conn.EXPECT().RemoteAddr().Return(addr).AnyTimes()
	conn.EXPECT().Close().Return(nil).MinTimes(1)
	dialer := mocksremote.NewMockDialer(ctrl)
	dialer.EXPECT().DialTimeout(gomock.Any(), gomock.Any(), gomock.Any()).Return(conn, nil).AnyTimes()
	opt.Dialer = dialer

	cli, err := NewClient(ctx, ri, hdlr, opt)
	test.Assert(t, err == nil, err)
	test.Assert(t, cli != nil, cli)

	err = cli.Recv(ctx, ri, nil)
	test.Assert(t, err == nil, err)
}

// TestRecycle test recycle
func TestRecycle(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	ctx := context.Background()

	addr := utils.NewNetAddr("tcp", "to")
	ri := newMockRPCInfo(addr)

	hdlr := mocksremote.NewMockClientTransHandler(ctrl)

	opt := newMockOption(ctrl, addr)

	cli, err := NewClient(ctx, ri, hdlr, opt)
	test.Assert(t, err == nil, err)
	test.Assert(t, cli != nil, cli)

	cli.Recycle()
	afterCli, ok := cli.(*client)
	test.Assert(t, ok)
	test.Assert(t, afterCli.transHdlr == nil)
	test.Assert(t, afterCli.conn == nil)
	test.Assert(t, afterCli.connManager == nil)
}
