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

import (
	"context"
	"errors"
	"fmt"

	"github.com/cloudwego/kitex/pkg/remote"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
	"github.com/cloudwego/kitex/pkg/serviceinfo"
)

const (
	// FrontMask is used in protocol sniffing.
	FrontMask = 0x0000ffff
)

// SetOrCheckMethodName is used to set method name to invocation.
func SetOrCheckMethodName(ctx context.Context, methodName string, message remote.Message) error {
	ri := message.RPCInfo()
	ink := ri.Invocation()
	callMethodName := ink.MethodName()
	if methodName == "" {
		return fmt.Errorf("method name that receive is empty")
	}
	if message.RPCRole() == remote.Client {
		if callMethodName == methodName {
			return nil
		}
		// the server's callMethodName may not be empty if RPCInfo is based on connection multiplexing
		// for the server side callMethodName ! = methodName is normal
		return fmt.Errorf("wrong method name, expect=%s, actual=%s", callMethodName, methodName)
	}
	inkSetter := ink.(rpcinfo.InvocationSetter)
	inkSetter.SetMethodName(methodName)
	var svcInfo *serviceinfo.ServiceInfo
	var methodInfo serviceinfo.MethodInfo
	// for ping pong server, svc info is nil until the request decoded
	svcSearcher := remote.GetServiceSearcher(ctx)
	svcInfo = svcSearcher.SearchService(ink.ServiceName(), methodName, false, ri.Config().PayloadCodec())
	if svcInfo == nil {
		return remote.NewTransErrorWithMsg(remote.UnknownService, fmt.Sprintf("unknown service %s, method %s", ink.ServiceName(), methodName))
	}
	if methodInfo = svcInfo.MethodInfo(context.Background(), methodName); methodInfo == nil {
		return remote.NewTransErrorWithMsg(remote.UnknownMethod, fmt.Sprintf("unknown method %s (service %s)", methodName, ink.ServiceName()))
	}
	inkSetter.SetPackageName(svcInfo.GetPackageName())
	inkSetter.SetServiceName(svcInfo.ServiceName)
	inkSetter.SetMethodInfo(methodInfo)

	// unknown method doesn't set methodName for RPCInfo.To(), or lead inconsistent with old version
	rpcinfo.AsMutableEndpointInfo(ri.To()).SetMethod(methodName)
	return nil
}

// SetOrCheckSeqID is used to check the sequence ID.
func SetOrCheckSeqID(seqID int32, message remote.Message) error {
	switch message.MessageType() {
	case remote.Call, remote.Oneway:
		if ink, ok := message.RPCInfo().Invocation().(rpcinfo.InvocationSetter); ok {
			ink.SetSeqID(seqID)
		} else {
			return errors.New("the interface Invocation doesn't implement InvocationSetter")
		}
	case remote.Reply:
		expectSeqID := message.RPCInfo().Invocation().SeqID()
		if expectSeqID != seqID {
			methodName := message.RPCInfo().Invocation().MethodName()
			return remote.NewTransErrorWithMsg(remote.BadSequenceID, fmt.Sprintf("method[%s] out of order sequence response, expect[%d], receive[%d]", methodName, expectSeqID, seqID))
		}
	case remote.Exception:
		// don't check, proxy may build Exception with seqID = 0
		// thrift 0.13 check seqID for Exception but thrift 0.9.2 doesn't check
	}
	return nil
}

// UpdateMsgType updates msg type.
func UpdateMsgType(msgType uint32, message remote.Message) error {
	rpcRole := message.RPCRole()
	mt := remote.MessageType(msgType)
	if mt == message.MessageType() {
		return nil
	}
	if rpcRole == remote.Server {
		if mt != remote.Call && mt != remote.Oneway && mt != remote.Stream {
			return remote.NewTransErrorWithMsg(remote.InvalidMessageTypeException, fmt.Sprintf("server side, invalid message type %d", mt))
		}
	} else {
		if mt != remote.Reply && mt != remote.Exception && mt != remote.Stream {
			return remote.NewTransErrorWithMsg(remote.InvalidMessageTypeException, fmt.Sprintf("client side, invalid message type %d", mt))
		}
	}

	message.SetMessageType(mt)
	return nil
}

// NewDataIfNeeded is used to create the data if not exist.
func NewDataIfNeeded(method string, message remote.Message) error {
	if message.Data() != nil {
		return nil
	}
	if message.NewData(method) {
		return nil
	}
	return remote.NewTransErrorWithMsg(remote.InternalError, "message data for codec is nil")
}
