package runtimegrpc

import (
	"context"
	"errors"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/federatedloginv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

type BeginFederatedLoginInvoker func(context.Context, *runtimev1.BeginFederatedLoginRequest, ...grpc.CallOption) (*runtimev1.BeginFederatedLoginResponse, error)
type ConsumeFederatedLoginInvoker func(context.Context, *runtimev1.ConsumeFederatedLoginRequest, ...grpc.CallOption) (*runtimev1.ConsumeFederatedLoginResponse, error)

// FederatedLoginTransactionClient projects the neutral one-time login-state
// lifecycle over Runtime. Product/provider request and response shapes remain
// owned by the calling AppService.
type FederatedLoginTransactionClient struct {
	begin   BeginFederatedLoginInvoker
	consume ConsumeFederatedLoginInvoker
}

func NewFederatedLoginTransactionClient(begin BeginFederatedLoginInvoker, consume ConsumeFederatedLoginInvoker) (*FederatedLoginTransactionClient, error) {
	if begin == nil || consume == nil {
		return nil, errors.New("runtimegrpc: complete federated login transaction invokers are required")
	}
	return &FederatedLoginTransactionClient{begin: begin, consume: consume}, nil
}

func (c *FederatedLoginTransactionClient) Begin(ctx context.Context, request semantic.BeginRequest) (semantic.BeginResponse, error) {
	if c == nil || c.begin == nil {
		return semantic.BeginResponse{}, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	wireRequest, err := mapping.ToWireBeginFederatedLoginRequest(ctx, request)
	if err != nil {
		return semantic.BeginResponse{}, err
	}
	response, err := c.begin(federatedLoginCallContext(ctx), wireRequest, federatedLoginCallOptions()...)
	if err != nil {
		return semantic.BeginResponse{}, mapping.FromFederatedLoginBeginStatusError(err)
	}
	return mapping.FromWireBeginFederatedLoginResponse(response)
}

func (c *FederatedLoginTransactionClient) Consume(ctx context.Context, request semantic.ConsumeRequest) error {
	if c == nil || c.consume == nil {
		return &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	wireRequest, err := mapping.ToWireConsumeFederatedLoginRequest(ctx, request)
	if err != nil {
		return err
	}
	response, err := c.consume(federatedLoginCallContext(ctx), wireRequest, federatedLoginCallOptions()...)
	if err != nil {
		return mapping.FromFederatedLoginConsumeStatusError(err)
	}
	_, err = mapping.FromWireConsumeFederatedLoginResponse(response)
	return err
}

func federatedLoginCallContext(ctx context.Context) context.Context {
	callCtx := withClientProtocol(ctx, semantic.ProtocolMajor, semantic.ProtocolMinor, semantic.CapabilityName)
	values, _ := metadata.FromOutgoingContext(callCtx)
	return metadata.NewOutgoingContext(callCtx, withoutSessionOutgoingMetadata(values.Copy()))
}

func federatedLoginCallOptions() []grpc.CallOption {
	return []grpc.CallOption{
		grpc.MaxCallSendMsgSize(envelope.MaxRequestWireBytes),
		grpc.MaxCallRecvMsgSize(envelope.MaxResponseWireBytes),
		grpc.WaitForReady(false),
	}
}
