/*
 * Copyright 2024 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 generic

import (
	"context"
	"fmt"

	"github.com/cloudwego/gopkg/bufiox"
	"github.com/cloudwego/gopkg/protocol/thrift/base"

	"github.com/cloudwego/kitex/pkg/generic/proto"
	"github.com/cloudwego/kitex/pkg/generic/thrift"
	codecProto "github.com/cloudwego/kitex/pkg/remote/codec/protobuf"
)

const (
	// BinaryThriftGenericV1PayloadCodecKey is used to be compatible with old binary thrift generic to get the payload codec.
	BinaryThriftGenericV1PayloadCodecKey = "binary_thrift_generic_v1_payload_codec"
	// GetMethodNameByRequestFuncKey is used to get method name by request for http generic.
	GetMethodNameByRequestFuncKey = "get_method_name_by_request_func"
	// IsBinaryGeneric is used to judge whether it is binary generic, except BinaryThriftGeneric.
	IsBinaryGeneric = "is_binary_generic"
)

// Args generic request
type Args struct {
	Request interface{}
	Method  string
	base    *base.Base
	inner   interface{}
}

var (
	_ codecProto.MessageWriterWithContext           = (*Args)(nil)
	_ codecProto.MessageReaderWithMethodWithContext = (*Args)(nil)
)

func (g *Args) SetCodec(inner interface{}) {
	g.inner = inner
}

func (g *Args) GetOrSetBase() interface{} {
	if g.base == nil {
		g.base = base.NewBase()
	}
	return g.base
}

// Write ...
func (g *Args) Write(ctx context.Context, method string, out bufiox.Writer) error {
	if err, ok := g.inner.(error); ok {
		return err
	}
	if w, ok := g.inner.(thrift.MessageWriter); ok {
		return w.Write(ctx, out, g.Request, method, true, g.base)
	}
	return fmt.Errorf("unexpected Args writer type: %T", g.inner)
}

func (g *Args) WritePb(ctx context.Context, method string) (interface{}, error) {
	if err, ok := g.inner.(error); ok {
		return nil, err
	}
	if w, ok := g.inner.(proto.MessageWriter); ok {
		return w.Write(ctx, g.Request, method, true)
	}
	return nil, fmt.Errorf("unexpected Args writer type: %T", g.inner)
}

// Read ...
func (g *Args) Read(ctx context.Context, method string, dataLen int, in bufiox.Reader) error {
	if err, ok := g.inner.(error); ok {
		return err
	}
	if rw, ok := g.inner.(thrift.MessageReader); ok {
		g.Method = method
		var err error
		g.Request, err = rw.Read(ctx, method, false, dataLen, in)
		return err
	}
	return fmt.Errorf("unexpected Args reader type: %T", g.inner)
}

func (g *Args) ReadPb(ctx context.Context, method string, in []byte) error {
	if err, ok := g.inner.(error); ok {
		return err
	}
	if w, ok := g.inner.(proto.MessageReader); ok {
		g.Method = method
		var err error
		g.Request, err = w.Read(ctx, method, false, in)
		return err
	}
	return fmt.Errorf("unexpected Args reader type: %T", g.inner)
}

// GetFirstArgument implements util.KitexArgs.
func (g *Args) GetFirstArgument() interface{} {
	return g.Request
}

// Result generic response
type Result struct {
	Success interface{}
	inner   interface{}
}

var (
	_ codecProto.MessageWriterWithContext           = (*Result)(nil)
	_ codecProto.MessageReaderWithMethodWithContext = (*Result)(nil)
)

// SetCodec ...
func (r *Result) SetCodec(inner interface{}) {
	r.inner = inner
}

// Write ...
func (r *Result) Write(ctx context.Context, method string, out bufiox.Writer) error {
	if err, ok := r.inner.(error); ok {
		return err
	}
	if w, ok := r.inner.(thrift.MessageWriter); ok {
		return w.Write(ctx, out, r.Success, method, false, nil)
	}
	return fmt.Errorf("unexpected Result writer type: %T", r.inner)
}

func (r *Result) WritePb(ctx context.Context, method string) (interface{}, error) {
	if err, ok := r.inner.(error); ok {
		return nil, err
	}
	if w, ok := r.inner.(proto.MessageWriter); ok {
		return w.Write(ctx, r.Success, method, false)
	}
	return nil, fmt.Errorf("unexpected Result writer type: %T", r.inner)
}

// Read ...
func (r *Result) Read(ctx context.Context, method string, dataLen int, in bufiox.Reader) error {
	if err, ok := r.inner.(error); ok {
		return err
	}
	if w, ok := r.inner.(thrift.MessageReader); ok {
		var err error
		r.Success, err = w.Read(ctx, method, true, dataLen, in)
		return err
	}
	return fmt.Errorf("unexpected Result reader type: %T", r.inner)
}

func (r *Result) ReadPb(ctx context.Context, method string, in []byte) error {
	if err, ok := r.inner.(error); ok {
		return err
	}
	if w, ok := r.inner.(proto.MessageReader); ok {
		var err error
		r.Success, err = w.Read(ctx, method, true, in)
		return err
	}
	return fmt.Errorf("unexpected Result reader type: %T", r.inner)
}

// GetSuccess implements util.KitexResult.
func (r *Result) GetSuccess() interface{} {
	if !r.IsSetSuccess() {
		return nil
	}
	return r.Success
}

// SetSuccess implements util.KitexResult.
func (r *Result) SetSuccess(x interface{}) {
	r.Success = x
}

// IsSetSuccess ...
func (r *Result) IsSetSuccess() bool {
	return r.Success != nil
}

// GetResult ...
func (r *Result) GetResult() interface{} {
	return r.Success
}

type ThriftWriter interface {
	Write(ctx context.Context, method string, w bufiox.Writer) error
}

type ThriftReader interface {
	Read(ctx context.Context, method string, dataLen int, r bufiox.Reader) error
}
