/*
 * Copyright 2022 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 nphttp2

import (
	"context"
	"net"
	"testing"
	"time"

	"github.com/cloudwego/kitex/internal/test"
	"github.com/cloudwego/kitex/pkg/kerrors"
	"github.com/cloudwego/kitex/pkg/remote"
	"github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/metadata"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
	"github.com/cloudwego/kitex/pkg/serviceinfo"
	"github.com/cloudwego/kitex/transport"
)

func TestClientHandler(t *testing.T) {
	// init
	opt := newMockClientOption()
	ctx := newMockCtxWithRPCInfo()
	msg := newMockNewMessage()
	ri := newMockRPCInfo()
	msg.RPCInfoFunc = func() rpcinfo.RPCInfo {
		return ri
	}
	mcfg := rpcinfo.AsMutableRPCConfig(ri.Config())
	mcfg.SetTransportProtocol(transport.PurePayload)
	mcfg.SetPayloadCodec(serviceinfo.Protobuf)
	conn, err := opt.ConnPool.Get(ctx, "tcp", mockAddr0, remote.ConnOption{Dialer: opt.Dialer, ConnectTimeout: time.Second})
	test.Assert(t, err == nil, err)

	// test NewTransHandler()
	cliTransHandler, err := NewCliTransHandlerFactory().NewTransHandler(opt)
	test.Assert(t, err == nil, err)

	// test Read(): comment now since Read will block until header has been received, which is not suitable for ut.
	// newMockStreamRecvHelloRequest(conn.(*clientConn).s)
	// ctx, err = cliTransHandler.Read(ctx, conn, msg)
	// test.Assert(t, err == nil, err)

	// test write()
	ctx, err = cliTransHandler.Write(ctx, conn, msg)
	test.Assert(t, ctx != nil, ctx)
	test.Assert(t, err == nil, err)
}

type mockCodec struct {
	remote.Codec
	encode func(ctx context.Context, msg remote.Message, out remote.ByteBuffer) error
	decode func(ctx context.Context, msg remote.Message, in remote.ByteBuffer) error
}

func (c *mockCodec) Encode(ctx context.Context, msg remote.Message, out remote.ByteBuffer) error {
	return c.encode(ctx, msg, out)
}

func (c *mockCodec) Decode(ctx context.Context, msg remote.Message, in remote.ByteBuffer) error {
	return c.decode(ctx, msg, in)
}

var (
	_ hasTrailer         = (*mockConnForRead)(nil)
	_ hasHeader          = (*mockConnForRead)(nil)
	_ hasGetRecvCompress = (*mockConnForRead)(nil)
	_ GRPCConn           = (*mockConnForRead)(nil)
)

type mockConnForRead struct {
	net.Conn
}

func (m *mockConnForRead) WriteFrame(hdr, data []byte) (n int, err error) {
	return 0, nil
}

func (m *mockConnForRead) ReadFrame() (hdr, data []byte, err error) {
	return nil, nil, nil
}

func (m *mockConnForRead) GetRecvCompress() string {
	return ""
}

func (m *mockConnForRead) Header() (metadata.MD, error) {
	return nil, nil
}

func (m *mockConnForRead) Trailer() metadata.MD {
	return nil
}

func Test_cliTransHandler_Read(t *testing.T) {
	t.Run("no error", func(t *testing.T) {
		h := &cliTransHandler{
			codec: &mockCodec{
				decode: func(ctx context.Context, msg remote.Message, out remote.ByteBuffer) error {
					return nil
				},
			},
		}
		msg := newMockNewMessage()
		ivk := rpcinfo.NewInvocation("service", "method")
		ri := rpcinfo.NewRPCInfo(nil, nil, ivk, nil, nil)
		msg.RPCInfoFunc = func() rpcinfo.RPCInfo {
			return ri
		}
		_, err := h.Read(context.Background(), &mockConnForRead{}, msg)
		test.Assert(t, err == nil, err)
		test.Assert(t, ivk.BizStatusErr() == nil)
	})

	t.Run("biz error", func(t *testing.T) {
		h := &cliTransHandler{
			codec: &mockCodec{
				decode: func(ctx context.Context, msg remote.Message, out remote.ByteBuffer) error {
					return kerrors.NewBizStatusError(100, "biz error")
				},
			},
		}
		msg := newMockNewMessage()
		ivk := rpcinfo.NewInvocation("service", "method")
		ri := rpcinfo.NewRPCInfo(nil, nil, ivk, nil, nil)
		msg.RPCInfoFunc = func() rpcinfo.RPCInfo {
			return ri
		}
		_, err := h.Read(context.Background(), &mockConnForRead{}, msg)
		test.Assert(t, err == nil, err)
		test.Assert(t, ivk.BizStatusErr().BizStatusCode() == 100)
	})
}
