/*
 * 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 (
	"bytes"
	"context"
	"errors"
	"io"
	"net"
	"reflect"
	"sync"
	"time"

	"github.com/cloudwego/netpoll"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/runtime/protoimpl"

	mockmessage "github.com/cloudwego/kitex/internal/mocks/message"
	"github.com/cloudwego/kitex/pkg/remote"
	"github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/grpc"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
	"github.com/cloudwego/kitex/pkg/serviceinfo"
	"github.com/cloudwego/kitex/pkg/utils"
)

var (
	frameHeaderLen = 9
	prefaceLen     = 24
	prefaceByte    = []byte("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
)

func newMockNpConn(address string) *mockNetpollConn {
	mc := &mockNetpollConn{
		mockConn: mockConn{
			RemoteAddrFunc: func() net.Addr { return utils.NewNetAddr("tcp", address) },
			WriteFunc: func(b []byte) (n int, err error) {
				// mock write preface
				return len(b), nil
			},
		},
		mockQueue: []mockFrame{},
	}
	mc.mockConn.ReadFunc = mc.mockReader
	mc.reader = &mockNetpollReader{reader: mc.mockConn}
	return mc
}

func (mc *mockNetpollConn) mockReader(b []byte) (n int, err error) {
	mc.counterLock.Lock()
	defer mc.counterLock.Unlock()
	bLen := len(b)
	// mock: handle bufio Read()
	if bLen == frameHeaderLen || bLen == defaultMockReadWriteBufferSize {
		b = b[0:0]
		if mc.queueCounter >= len(mc.mockQueue) {
			return
		}
		fr := mc.mockQueue[mc.queueCounter]
		frame := fr.header
		b = append(b, frame...)
		return len(b), nil
	}
	if bLen == prefaceLen {
		b = b[0:0]
		b = append(b, prefaceByte...)
		return len(b), nil
	}

	if mc.queueCounter < len(mc.mockQueue) {
		fr := mc.mockQueue[mc.queueCounter]
		mc.queueCounter++
		body := fr.body
		if body != nil && len(body) > 0 {
			b = b[0:0]
			b = append(b, body...)
		}
	}

	return len(b), nil
}

type mockFrame struct {
	header []byte
	body   []byte
}

var (
	headerPayload    = []byte{131, 134, 69, 147, 99, 21, 149, 146, 249, 105, 58, 248, 172, 41, 82, 91, 24, 220, 63, 88, 203, 69, 7, 65, 139, 234, 100, 151, 202, 243, 89, 89, 23, 144, 180, 159, 95, 139, 29, 117, 208, 98, 13, 38, 61, 76, 77, 101, 100, 122, 137, 234, 100, 151, 203, 29, 192, 184, 151, 7, 64, 2, 116, 101, 134, 77, 131, 53, 5, 177, 31, 64, 137, 154, 202, 200, 178, 77, 73, 79, 106, 127, 134, 125, 247, 217, 124, 86, 255, 64, 138, 65, 237, 176, 133, 89, 5, 179, 185, 136, 95, 139, 234, 100, 151, 202, 243, 89, 89, 23, 144, 180, 159, 64, 137, 65, 237, 176, 133, 90, 146, 166, 115, 201, 0, 64, 136, 242, 178, 82, 181, 7, 152, 210, 127, 164, 0, 130, 227, 96, 109, 150, 93, 105, 151, 157, 124, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 150, 89, 64, 48, 54, 228, 146, 190, 16, 134, 50, 203, 64, 135, 65, 237, 176, 133, 88, 181, 119, 131, 174, 195, 201, 64, 143, 143, 210, 75, 73, 81, 58, 210, 154, 132, 150, 197, 147, 234, 178, 255, 131, 154, 202, 201, 64, 141, 144, 168, 73, 170, 26, 76, 122, 150, 65, 108, 238, 98, 23, 139, 234, 100, 151, 202, 243, 89, 89, 23, 144, 180, 159, 64, 141, 144, 168, 73, 170, 26, 76, 122, 150, 164, 169, 156, 242, 127, 134, 220, 63, 88, 203, 69, 7, 64, 138, 144, 168, 73, 170, 26, 76, 122, 150, 52, 132, 1, 45, 64, 138, 65, 237, 176, 133, 88, 148, 90, 132, 150, 207, 133, 144, 178, 142, 218, 19, 64, 135, 65, 237, 176, 133, 88, 210, 19, 1, 45}
	mockHeaderFrame  = []byte{0, 1, 42, 1, 4, 0, 0, 0, 1}
	mockSettingFrame = []byte{0, 0, 6, 4, 0, 0, 0, 0, 0}
)

func (mc *mockNetpollConn) mockSettingFrame() {
	mc.counterLock.Lock()
	defer mc.counterLock.Unlock()
	mc.mockQueue = append(mc.mockQueue, mockFrame{mockSettingFrame, nil})
}

func (mc *mockNetpollConn) mockMetaHeaderFrame() {
	mc.counterLock.Lock()
	defer mc.counterLock.Unlock()
	mc.mockQueue = append(mc.mockQueue, mockFrame{mockHeaderFrame, headerPayload})
}

var _ netpoll.Connection = &mockNetpollConn{}

// mockNetpollConn implements netpoll.Connection.
type mockNetpollConn struct {
	mockConn
	mockQueue    []mockFrame
	queueCounter int
	counterLock  sync.Mutex
	reader       *mockNetpollReader
}

func (m *mockNetpollConn) Reader() netpoll.Reader {
	return m.reader
}

func (m *mockNetpollConn) Writer() netpoll.Writer {
	panic("implement me")
}

func (m *mockNetpollConn) IsActive() bool {
	panic("implement me")
}

func (m *mockNetpollConn) SetReadTimeout(timeout time.Duration) error {
	return nil
}

func (m *mockNetpollConn) SetWriteTimeout(timeout time.Duration) error {
	return nil
}

func (m *mockNetpollConn) SetIdleTimeout(timeout time.Duration) error {
	return nil
}

func (m *mockNetpollConn) SetOnRequest(on netpoll.OnRequest) error {
	return nil
}

func (m *mockNetpollConn) AddCloseCallback(callback netpoll.CloseCallback) error {
	return nil
}

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

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

var _ netpoll.Reader = &mockNetpollReader{}

// mockNetpollReader implements netpoll.Reader.
type mockNetpollReader struct {
	reader io.Reader
}

func (m *mockNetpollReader) Next(n int) (p []byte, err error) {
	p = make([]byte, n)
	_, err = m.reader.Read(p)
	if err != nil {
		return nil, err
	}
	return p, nil
}

func (m *mockNetpollReader) Peek(n int) (buf []byte, err error)        { return }
func (m *mockNetpollReader) Skip(n int) (err error)                    { return }
func (m *mockNetpollReader) Until(delim byte) (line []byte, err error) { return }
func (m *mockNetpollReader) ReadString(n int) (s string, err error)    { return }
func (m *mockNetpollReader) ReadBinary(n int) (p []byte, err error)    { return }
func (m *mockNetpollReader) ReadByte() (b byte, err error)             { return }
func (m *mockNetpollReader) Slice(n int) (r netpoll.Reader, err error) { return }
func (m *mockNetpollReader) Release() (err error)                      { return }
func (m *mockNetpollReader) Len() (length int)                         { return }

var _ net.Conn = &mockConn{}

// mockConn implements the net.Conn interface.
type mockConn struct {
	ReadFunc             func(b []byte) (n int, err error)
	WriteFunc            func(b []byte) (n int, err error)
	CloseFunc            func() (e error)
	LocalAddrFunc        func() (r net.Addr)
	RemoteAddrFunc       func() (r net.Addr)
	SetDeadlineFunc      func(t time.Time) (e error)
	SetReadDeadlineFunc  func(t time.Time) (e error)
	SetWriteDeadlineFunc func(t time.Time) (e error)
}

// Read implements the net.Conn interface.
func (m mockConn) Read(b []byte) (n int, err error) {
	if m.ReadFunc != nil {
		return m.ReadFunc(b)
	}
	return
}

// Write implements the net.Conn interface.
func (m mockConn) Write(b []byte) (n int, err error) {
	if m.WriteFunc != nil {
		return m.WriteFunc(b)
	}
	return
}

// Close implements the net.Conn interface.
func (m mockConn) Close() (e error) {
	if m.CloseFunc != nil {
		return m.CloseFunc()
	}
	return
}

// LocalAddr implements the net.Conn interface.
func (m mockConn) LocalAddr() (r net.Addr) {
	if m.LocalAddrFunc != nil {
		return m.LocalAddrFunc()
	}
	return
}

// RemoteAddr implements the net.Conn interface.
func (m mockConn) RemoteAddr() (r net.Addr) {
	if m.RemoteAddrFunc != nil {
		return m.RemoteAddrFunc()
	}
	return
}

// SetDeadline implements the net.Conn interface.
func (m mockConn) SetDeadline(t time.Time) (e error) {
	if m.SetDeadlineFunc != nil {
		return m.SetDeadlineFunc(t)
	}
	return
}

// SetReadDeadline implements the net.Conn interface.
func (m mockConn) SetReadDeadline(t time.Time) (e error) {
	if m.SetReadDeadlineFunc != nil {
		return m.SetReadDeadlineFunc(t)
	}
	return
}

// SetWriteDeadline implements the net.Conn interface.
func (m mockConn) SetWriteDeadline(t time.Time) (e error) {
	if m.SetWriteDeadlineFunc != nil {
		return m.SetWriteDeadlineFunc(t)
	}
	return
}

var (
	mockAddr0 = "127.0.0.1:28000"
	mockAddr1 = "127.0.0.1:28001"
)

const defaultMockReadWriteBufferSize = 16

func newMockConnPool() *connPool {
	connPool := NewConnPool("test", uint32(0), grpc.ConnectOptions{
		KeepaliveParams:       grpc.ClientKeepalive{},
		InitialWindowSize:     0,
		InitialConnWindowSize: 0,
		WriteBufferSize:       defaultMockReadWriteBufferSize,
		ReadBufferSize:        defaultMockReadWriteBufferSize,
		MaxHeaderListSize:     nil,
	})
	return connPool
}

func newMockConnOption() remote.ConnOption {
	return remote.ConnOption{Dialer: newMockDialer(), ConnectTimeout: time.Second}
}

func newMockServerOption() *remote.ServerOption {
	return &remote.ServerOption{
		SvcSearcher:           nil,
		TransServerFactory:    nil,
		SvrHandlerFactory:     nil,
		Codec:                 nil,
		PayloadCodec:          nil,
		Address:               nil,
		ReusePort:             false,
		ExitWaitTime:          0,
		AcceptFailedDelayTime: 0,
		MaxConnectionIdleTime: 0,
		ReadWriteTimeout:      0,
		InitOrResetRPCInfoFunc: func(ri rpcinfo.RPCInfo, addr net.Addr) rpcinfo.RPCInfo {
			return newMockRPCInfo()
		},
		TracerCtl: &rpcinfo.TraceController{},
		GRPCCfg: &grpc.ServerConfig{
			MaxStreams:                 0,
			KeepaliveParams:            grpc.ServerKeepalive{},
			KeepaliveEnforcementPolicy: grpc.EnforcementPolicy{},
			InitialWindowSize:          0,
			InitialConnWindowSize:      0,
			MaxHeaderListSize:          nil,
		},
		Option: remote.Option{},
	}
}

func newMockClientOption() *remote.ClientOption {
	return &remote.ClientOption{
		SvcInfo:           nil,
		CliHandlerFactory: nil,
		Codec:             nil,
		PayloadCodec:      nil,
		ConnPool:          newMockConnPool(),
		Dialer:            newMockDialer(),
		Option: remote.Option{
			Outbounds:             nil,
			Inbounds:              nil,
			StreamingMetaHandlers: nil,
		},
		EnableConnPoolReporter: false,
	}
}

func newMockDialer() *remote.SynthesizedDialer {
	d := &remote.SynthesizedDialer{}
	d.DialFunc = func(network, address string, timeout time.Duration) (net.Conn, error) {
		connectCost := time.Millisecond * 10
		if timeout < connectCost {
			return nil, errors.New("connect timeout")
		}
		npConn := newMockNpConn(address)
		npConn.mockSettingFrame()
		return npConn, nil
	}
	return d
}

func newMockDialerWithDialFunc(dialFunc func(network, address string, timeout time.Duration) (net.Conn, error)) *remote.SynthesizedDialer {
	d := &remote.SynthesizedDialer{}
	d.DialFunc = dialFunc
	return d
}

func newMockCtxWithRPCInfo() context.Context {
	return rpcinfo.NewCtxWithRPCInfo(context.Background(), newMockRPCInfo())
}

func newMockRPCInfo() rpcinfo.RPCInfo {
	method := "method"
	c := rpcinfo.NewEndpointInfo("", method, nil, nil)
	endpointTags := map[string]string{}
	endpointTags[rpcinfo.HTTPURL] = "https://github.com/cloudwego/kitex"
	s := rpcinfo.NewEndpointInfo("", method, nil, endpointTags)
	ink := rpcinfo.NewInvocation("", method)
	cfg := rpcinfo.NewRPCConfig()
	rpcinfo.AsMutableRPCConfig(cfg).SetPayloadCodec(serviceinfo.Protobuf)
	ri := rpcinfo.NewRPCInfo(c, s, ink, cfg, rpcinfo.NewRPCStats())
	return ri
}

func newMockNewMessage() *mockmessage.MockMessage {
	return &mockmessage.MockMessage{
		DataFunc: func() interface{} {
			return &HelloRequest{
				state:         protoimpl.MessageState{},
				sizeCache:     0,
				unknownFields: nil,
				Name:          "test",
			}
		},
	}
}

func newMockServerTransport(npConn *mockNetpollConn) (grpc.ServerTransport, error) {
	opt := newMockServerOption()
	tr, err := grpc.NewServerTransport(context.Background(), npConn, opt.GRPCCfg)
	return tr, err
}

func mockStreamRecv(s *grpc.Stream, data string) {
	buffer := new(bytes.Buffer)
	buffer.Reset()
	buffer.Write([]byte(data))
	grpc.StreamWrite(s, buffer)
}

func newMockStreamRecvHelloRequest(s *grpc.Stream) {
	hdr := []byte{0, 0, 0, 0, 6, 10, 4, 116, 101, 115, 116, 0}
	buffer := new(bytes.Buffer)
	buffer.Reset()
	buffer.Write(hdr)
	grpc.StreamWrite(s, buffer)
}

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.27.1
// 	protoc        v3.19.4
// source: message.proto

const (
	// Verify that this generated code is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
	// Verify that runtime/protoimpl is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

// The request message containing the user's name.
type HelloRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}

func (x *HelloRequest) Reset() {
	*x = HelloRequest{}
	if protoimpl.UnsafeEnabled {
		mi := &file_message_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *HelloRequest) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*HelloRequest) ProtoMessage() {}

func (x *HelloRequest) ProtoReflect() protoreflect.Message {
	mi := &file_message_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
func (*HelloRequest) Descriptor() ([]byte, []int) {
	return file_message_proto_rawDescGZIP(), []int{0}
}

func (x *HelloRequest) GetName() string {
	if x != nil {
		return x.Name
	}
	return ""
}

// The response message containing the greetings
type HelloReply struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}

func (x *HelloReply) Reset() {
	*x = HelloReply{}
	if protoimpl.UnsafeEnabled {
		mi := &file_message_proto_msgTypes[1]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *HelloReply) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*HelloReply) ProtoMessage() {}

func (x *HelloReply) ProtoReflect() protoreflect.Message {
	mi := &file_message_proto_msgTypes[1]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
func (*HelloReply) Descriptor() ([]byte, []int) {
	return file_message_proto_rawDescGZIP(), []int{1}
}

func (x *HelloReply) GetMessage() string {
	if x != nil {
		return x.Message
	}
	return ""
}

var File_message_proto protoreflect.FileDescriptor

var file_message_proto_rawDesc = []byte{
	0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
	0x08, 0x47, 0x72, 0x70, 0x63, 0x44, 0x65, 0x6d, 0x6f, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c,
	0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a,
	0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d,
	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65,
	0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x45, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72,
	0x12, 0x3a, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x16, 0x2e, 0x47,
	0x72, 0x70, 0x63, 0x44, 0x65, 0x6d, 0x6f, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71,
	0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x47, 0x72, 0x70, 0x63, 0x44, 0x65, 0x6d, 0x6f, 0x2e,
	0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x10, 0x5a, 0x0e,
	0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x6b, 0x69, 0x74, 0x65, 0x78, 0x5f, 0x67, 0x65, 0x6e, 0x62, 0x06,
	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}

var (
	file_message_proto_rawDescOnce sync.Once
	file_message_proto_rawDescData = file_message_proto_rawDesc
)

func file_message_proto_rawDescGZIP() []byte {
	file_message_proto_rawDescOnce.Do(func() {
		file_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_message_proto_rawDescData)
	})
	return file_message_proto_rawDescData
}

var (
	file_message_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
	file_message_proto_goTypes  = []interface{}{
		(*HelloRequest)(nil), // 0: GrpcDemo.HelloRequest
		(*HelloReply)(nil),   // 1: GrpcDemo.HelloReply
	}
)

var file_message_proto_depIdxs = []int32{
	0, // 0: GrpcDemo.Greeter.SayHello:input_type -> GrpcDemo.HelloRequest
	1, // 1: GrpcDemo.Greeter.SayHello:output_type -> GrpcDemo.HelloReply
	1, // [1:2] is the sub-list for method output_type
	0, // [0:1] is the sub-list for method input_type
	0, // [0:0] is the sub-list for extension type_name
	0, // [0:0] is the sub-list for extension extendee
	0, // [0:0] is the sub-list for field type_name
}

func init() { file_message_proto_init() }
func file_message_proto_init() {
	if File_message_proto != nil {
		return
	}
	if !protoimpl.UnsafeEnabled {
		file_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*HelloRequest); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_message_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*HelloReply); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
	}
	type x struct{}
	out := protoimpl.TypeBuilder{
		File: protoimpl.DescBuilder{
			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
			RawDescriptor: file_message_proto_rawDesc,
			NumEnums:      0,
			NumMessages:   2,
			NumExtensions: 0,
			NumServices:   1,
		},
		GoTypes:           file_message_proto_goTypes,
		DependencyIndexes: file_message_proto_depIdxs,
		MessageInfos:      file_message_proto_msgTypes,
	}.Build()
	File_message_proto = out.File
	file_message_proto_rawDesc = nil
	file_message_proto_goTypes = nil
	file_message_proto_depIdxs = nil
}

var _ context.Context

// Code generated by Kitex v1.8.0. DO NOT EDIT.
