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

import (
	"context"
	"net"

	"github.com/cloudwego/kitex/pkg/kerrors"
	"github.com/cloudwego/kitex/pkg/remote"
	"github.com/cloudwego/kitex/pkg/remote/codec/grpc"
	"github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/metadata"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
)

type cliTransHandlerFactory struct{}

// NewCliTransHandlerFactory ...
func NewCliTransHandlerFactory() remote.ClientTransHandlerFactory {
	return &cliTransHandlerFactory{}
}

func (f *cliTransHandlerFactory) NewTransHandler(opt *remote.ClientOption) (remote.ClientTransHandler, error) {
	return newCliTransHandler(opt)
}

func newCliTransHandler(opt *remote.ClientOption) (*cliTransHandler, error) {
	return &cliTransHandler{
		opt:   opt,
		codec: grpc.NewGRPCCodec(grpc.WithThriftCodec(opt.PayloadCodec)),
	}, nil
}

var _ remote.ClientTransHandler = &cliTransHandler{}

type cliTransHandler struct {
	opt   *remote.ClientOption
	codec remote.Codec
}

func (h *cliTransHandler) Write(ctx context.Context, conn net.Conn, msg remote.Message) (nctx context.Context, err error) {
	buf := newBuffer(conn.(*clientConn))
	defer buf.Release(err)

	if err = h.codec.Encode(ctx, msg, buf); err != nil {
		return ctx, err
	}
	return ctx, buf.Flush()
}

func (h *cliTransHandler) Read(ctx context.Context, conn net.Conn, msg remote.Message) (nctx context.Context, err error) {
	buf := newBuffer(conn.(GRPCConn))
	defer buf.Release(err)

	// set recv grpc compressor at client to decode the pack from server
	ri := msg.RPCInfo()
	remote.SetRecvCompressor(ri, conn.(hasGetRecvCompress).GetRecvCompress())
	err = h.codec.Decode(ctx, msg, buf)
	if bizStatusErr, isBizErr := kerrors.FromBizStatusError(err); isBizErr {
		if setter, ok := ri.Invocation().(rpcinfo.InvocationSetter); ok {
			setter.SetBizStatusErr(bizStatusErr)
			return ctx, nil
		}
	}
	ctx = receiveHeaderAndTrailer(ctx, conn)
	return ctx, err
}

func (h *cliTransHandler) OnRead(ctx context.Context, conn net.Conn) (context.Context, error) {
	// do nothing
	hdr, err := conn.(*clientConn).Header()
	if err != nil {
		return nil, err
	}

	return metadata.NewIncomingContext(ctx, hdr), nil
}

func (h *cliTransHandler) OnConnect(ctx context.Context) (context.Context, error) {
	return ctx, nil
}

func (h *cliTransHandler) OnInactive(ctx context.Context, conn net.Conn) {
	panic("unimplemented")
}

func (h *cliTransHandler) OnError(ctx context.Context, err error, conn net.Conn) {
	panic("unimplemented")
}

func (h *cliTransHandler) OnMessage(ctx context.Context, args, result remote.Message) (context.Context, error) {
	// do nothing
	return ctx, nil
}

func (h *cliTransHandler) SetPipeline(pipeline *remote.TransPipeline) {
}
