/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 proxy_factory

import (
	"context"
	"reflect"
)

import (
	"github.com/dubbogo/gost/log/logger"

	perrors "github.com/pkg/errors"
)

import (
	"dubbo.apache.org/dubbo-go/v3/common"
	"dubbo.apache.org/dubbo-go/v3/common/constant"
	"dubbo.apache.org/dubbo-go/v3/common/extension"
	"dubbo.apache.org/dubbo-go/v3/protocol/base"
	"dubbo.apache.org/dubbo-go/v3/protocol/result"
	"dubbo.apache.org/dubbo-go/v3/proxy"
)

func init() {
	extension.SetProxyFactory(constant.PassThroughProxyFactoryKey, NewPassThroughProxyFactory)
}

// PassThroughProxyFactory is the factory of PassThroughProxyInvoker
type PassThroughProxyFactory struct {
}

// NewPassThroughProxyFactory returns a proxy factory instance
func NewPassThroughProxyFactory(_ ...proxy.Option) proxy.ProxyFactory {
	return &PassThroughProxyFactory{}
}

// GetProxy gets a proxy
func (factory *PassThroughProxyFactory) GetProxy(invoker base.Invoker, url *common.URL) *proxy.Proxy {
	return factory.GetAsyncProxy(invoker, nil, url)
}

// GetAsyncProxy gets a async proxy
func (factory *PassThroughProxyFactory) GetAsyncProxy(invoker base.Invoker, callBack any, url *common.URL) *proxy.Proxy {
	//create proxy
	attachments := map[string]string{}
	attachments[constant.AsyncKey] = url.GetParam(constant.AsyncKey, "false")
	return proxy.NewProxy(invoker, callBack, attachments)
}

// GetInvoker gets a invoker
func (factory *PassThroughProxyFactory) GetInvoker(url *common.URL) base.Invoker {
	return &PassThroughProxyInvoker{
		ProxyInvoker: &ProxyInvoker{
			BaseInvoker: *base.NewBaseInvoker(url),
		},
	}
}

// PassThroughProxyInvoker is a invoker struct, it calls service with specific method 'Serivce' and params:
// Service(method string, argsTypes []string, args [][]byte, attachment map[string]any)
// PassThroughProxyInvoker pass through raw invocation data and method name to service, which will deal with them.
type PassThroughProxyInvoker struct {
	*ProxyInvoker
}

// Invoke is used to call service method by invocation
func (pi *PassThroughProxyInvoker) Invoke(ctx context.Context, invocation base.Invocation) result.Result {
	result := &result.RPCResult{}
	result.SetAttachments(invocation.Attachments())
	url := getProviderURL(pi.GetURL())

	arguments := invocation.Arguments()
	srv := common.ServiceMap.GetServiceByServiceKey(url.Protocol, url.ServiceKey())

	var args [][]byte
	if len(arguments) > 0 {
		args = make([][]byte, 0, len(arguments))
		for _, arg := range arguments {
			if v, ok := arg.([]byte); ok {
				args = append(args, v)
			} else {
				result.Err = perrors.New("the param type is not []byte")
				return result
			}
		}
	}
	method := srv.Method()["Service"]

	in := make([]reflect.Value, 0, 5)
	in = append(in, srv.Service())
	in = append(in, reflect.ValueOf(invocation.MethodName()))
	in = append(in, reflect.ValueOf(invocation.GetAttachmentInterface(constant.ParamsTypeKey)))
	in = append(in, reflect.ValueOf(args))
	in = append(in, reflect.ValueOf(invocation.Attachments()))

	var replyv reflect.Value
	var retErr any

	returnValues, callErr := callLocalMethod(method.Method(), in)

	if callErr != nil {
		logger.Errorf("Invoke function error: %+v, service: %#v", callErr, url)
		result.SetError(callErr)
		return result
	}

	replyv = returnValues[0]
	retErr = returnValues[1].Interface()

	if retErr != nil {
		result.SetError(retErr.(error))
		return result
	}
	if replyv.IsValid() && (replyv.Kind() != reflect.Ptr || replyv.Kind() == reflect.Ptr && replyv.Elem().IsValid()) {
		result.SetResult(replyv.Interface())
	}

	return result
}
