// The MIT License
//
// Copyright (c) 2021 Temporal Technologies Inc.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package internal

import (
	"context"
	"time"

	commonpb "go.temporal.io/api/common/v1"
	"go.temporal.io/sdk/converter"
	"go.temporal.io/sdk/internal/common/metrics"
	"go.temporal.io/sdk/log"
)

// Interceptor is a common interface for all interceptors. See documentation in
// the interceptor package for more details.
type Interceptor interface {
	ClientInterceptor
	WorkerInterceptor
}

// WorkerInterceptor is a common interface for all interceptors. See
// documentation in the interceptor package for more details.
type WorkerInterceptor interface {
	// InterceptActivity is called before each activity interception needed with
	// the next interceptor in the chain.
	InterceptActivity(ctx context.Context, next ActivityInboundInterceptor) ActivityInboundInterceptor

	// InterceptWorkflow is called before each workflow interception needed with
	// the next interceptor in the chain.
	InterceptWorkflow(ctx Context, next WorkflowInboundInterceptor) WorkflowInboundInterceptor

	mustEmbedWorkerInterceptorBase()
}

// ActivityInboundInterceptor is an interface for all activity calls originating
// from the server. See documentation in the interceptor package for more
// details.
type ActivityInboundInterceptor interface {
	// Init is the first call of this interceptor. Implementations can change/wrap
	// the outbound interceptor before calling Init on the next interceptor.
	Init(outbound ActivityOutboundInterceptor) error

	// ExecuteActivity is called when an activity is to be run on this worker.
	// interceptor.Header will return a non-nil map for this context.
	ExecuteActivity(ctx context.Context, in *ExecuteActivityInput) (interface{}, error)

	mustEmbedActivityInboundInterceptorBase()
}

// ExecuteActivityInput is the input to ActivityInboundInterceptor.ExecuteActivity.
type ExecuteActivityInput struct {
	Args []interface{}
}

// ActivityOutboundInterceptor is an interface for all activity calls
// originating from the SDK. See documentation in the interceptor package for
// more details.
type ActivityOutboundInterceptor interface {
	// GetInfo intercepts activity.GetInfo.
	GetInfo(ctx context.Context) ActivityInfo

	// GetLogger intercepts activity.GetLogger.
	GetLogger(ctx context.Context) log.Logger

	// GetMetricsHandler intercepts activity.GetMetricsHandler.
	GetMetricsHandler(ctx context.Context) metrics.Handler

	// RecordHeartbeat intercepts activity.RecordHeartbeat.
	RecordHeartbeat(ctx context.Context, details ...interface{})

	// HasHeartbeatDetails intercepts activity.HasHeartbeatDetails.
	HasHeartbeatDetails(ctx context.Context) bool

	// GetHeartbeatDetails intercepts activity.GetHeartbeatDetails.
	GetHeartbeatDetails(ctx context.Context, d ...interface{}) error

	// GetWorkerStopChannel intercepts activity.GetWorkerStopChannel.
	GetWorkerStopChannel(ctx context.Context) <-chan struct{}

	mustEmbedActivityOutboundInterceptorBase()
}

// WorkflowInboundInterceptor is an interface for all workflow calls originating
// from the server. See documentation in the interceptor package for more
// details.
type WorkflowInboundInterceptor interface {
	// Init is the first call of this interceptor. Implementations can change/wrap
	// the outbound interceptor before calling Init on the next interceptor.
	Init(outbound WorkflowOutboundInterceptor) error

	// ExecuteWorkflow is called when a workflow is to be run on this worker.
	// interceptor.WorkflowHeader will return a non-nil map for this context.
	ExecuteWorkflow(ctx Context, in *ExecuteWorkflowInput) (interface{}, error)

	// HandleSignal is called when a signal is sent to a workflow on this worker.
	// interceptor.WorkflowHeader will return a non-nil map for this context.
	HandleSignal(ctx Context, in *HandleSignalInput) error

	// HandleQuery is called when a query is sent to a workflow on this worker.
	// interceptor.WorkflowHeader will return a non-nil map for this context.
	HandleQuery(ctx Context, in *HandleQueryInput) (interface{}, error)

	mustEmbedWorkflowInboundInterceptorBase()
}

// ExecuteWorkflowInput is the input to
// WorkflowInboundInterceptor.ExecuteWorkflow.
type ExecuteWorkflowInput struct {
	Args []interface{}
}

// HandleSignalInput is the input to WorkflowInboundInterceptor.HandleSignal.
type HandleSignalInput struct {
	SignalName string
	// Arg is the signal argument. It is presented as a primitive payload since
	// the type needed for decode is not available at the time of interception.
	Arg *commonpb.Payloads
}

// HandleQueryInput is the input to WorkflowInboundInterceptor.HandleQuery.
type HandleQueryInput struct {
	QueryType string
	Args      []interface{}
}

// WorkflowOutboundInterceptor is an interface for all workflow calls
// originating from the SDK. See documentation in the interceptor package for
// more details.
type WorkflowOutboundInterceptor interface {
	// Go intercepts workflow.Go.
	Go(ctx Context, name string, f func(ctx Context)) Context

	// ExecuteActivity intercepts workflow.ExecuteActivity.
	// interceptor.WorkflowHeader will return a non-nil map for this context.
	ExecuteActivity(ctx Context, activityType string, args ...interface{}) Future

	// ExecuteLocalActivity intercepts workflow.ExecuteLocalActivity.
	// interceptor.WorkflowHeader will return a non-nil map for this context.
	ExecuteLocalActivity(ctx Context, activityType string, args ...interface{}) Future

	// ExecuteChildWorkflow intercepts workflow.ExecuteChildWorkflow.
	// interceptor.WorkflowHeader will return a non-nil map for this context.
	ExecuteChildWorkflow(ctx Context, childWorkflowType string, args ...interface{}) ChildWorkflowFuture

	// GetInfo intercepts workflow.GetInfo.
	GetInfo(ctx Context) *WorkflowInfo

	// GetLogger intercepts workflow.GetLogger.
	GetLogger(ctx Context) log.Logger

	// GetMetricsHandler intercepts workflow.GetMetricsHandler.
	GetMetricsHandler(ctx Context) metrics.Handler

	// Now intercepts workflow.Now.
	Now(ctx Context) time.Time

	// NewTimer intercepts workflow.NewTimer.
	NewTimer(ctx Context, d time.Duration) Future

	// Sleep intercepts workflow.Sleep.
	Sleep(ctx Context, d time.Duration) (err error)

	// RequestCancelExternalWorkflow intercepts
	// workflow.RequestCancelExternalWorkflow.
	RequestCancelExternalWorkflow(ctx Context, workflowID, runID string) Future

	// SignalExternalWorkflow intercepts workflow.SignalExternalWorkflow.
	// interceptor.WorkflowHeader will return a non-nil map for this context.
	SignalExternalWorkflow(ctx Context, workflowID, runID, signalName string, arg interface{}) Future

	// SignalChildWorkflow intercepts
	// workflow.ChildWorkflowFuture.SignalChildWorkflow.
	// interceptor.WorkflowHeader will return a non-nil map for this context.
	SignalChildWorkflow(ctx Context, workflowID, signalName string, arg interface{}) Future

	// UpsertSearchAttributes intercepts workflow.UpsertSearchAttributes.
	UpsertSearchAttributes(ctx Context, attributes map[string]interface{}) error

	// GetSignalChannel intercepts workflow.GetSignalChannel.
	GetSignalChannel(ctx Context, signalName string) ReceiveChannel

	// SideEffect intercepts workflow.SideEffect.
	SideEffect(ctx Context, f func(ctx Context) interface{}) converter.EncodedValue

	// MutableSideEffect intercepts workflow.MutableSideEffect.
	MutableSideEffect(
		ctx Context,
		id string,
		f func(ctx Context) interface{},
		equals func(a, b interface{}) bool,
	) converter.EncodedValue

	// GetVersion intercepts workflow.GetVersion.
	GetVersion(ctx Context, changeID string, minSupported, maxSupported Version) Version

	// SetQueryHandler intercepts workflow.SetQueryHandler.
	SetQueryHandler(ctx Context, queryType string, handler interface{}) error

	// IsReplaying intercepts workflow.IsReplaying.
	IsReplaying(ctx Context) bool

	// HasLastCompletionResult intercepts workflow.HasLastCompletionResult.
	HasLastCompletionResult(ctx Context) bool

	// GetLastCompletionResult intercepts workflow.GetLastCompletionResult.
	GetLastCompletionResult(ctx Context, d ...interface{}) error

	// GetLastError intercepts workflow.GetLastError.
	GetLastError(ctx Context) error

	// NewContinueAsNewError intercepts workflow.NewContinueAsNewError.
	// interceptor.WorkflowHeader will return a non-nil map for this context.
	NewContinueAsNewError(ctx Context, wfn interface{}, args ...interface{}) error

	mustEmbedWorkflowOutboundInterceptorBase()
}

// ClientInterceptor for providing a ClientOutboundInterceptor to intercept
// certain workflow-specific client calls from the SDK. See documentation in the
// interceptor package for more details.
type ClientInterceptor interface {
	// This is called on client creation if set via client options
	InterceptClient(next ClientOutboundInterceptor) ClientOutboundInterceptor

	mustEmbedClientInterceptorBase()
}

// ClientOutboundInterceptor is an interface for certain workflow-specific calls
// originating from the SDK. See documentation in the interceptor package for
// more details.
type ClientOutboundInterceptor interface {
	// ExecuteWorkflow intercepts client.Client.ExecuteWorkflow.
	// interceptor.Header will return a non-nil map for this context.
	ExecuteWorkflow(context.Context, *ClientExecuteWorkflowInput) (WorkflowRun, error)

	// SignalWorkflow intercepts client.Client.SignalWorkflow.
	// interceptor.Header will return a non-nil map for this context.
	SignalWorkflow(context.Context, *ClientSignalWorkflowInput) error

	// SignalWithStartWorkflow intercepts client.Client.SignalWithStartWorkflow.
	// interceptor.Header will return a non-nil map for this context.
	SignalWithStartWorkflow(context.Context, *ClientSignalWithStartWorkflowInput) (WorkflowRun, error)

	// CancelWorkflow intercepts client.Client.CancelWorkflow.
	CancelWorkflow(context.Context, *ClientCancelWorkflowInput) error

	// TerminateWorkflow intercepts client.Client.TerminateWorkflow.
	TerminateWorkflow(context.Context, *ClientTerminateWorkflowInput) error

	// QueryWorkflow intercepts client.Client.QueryWorkflow.
	// interceptor.Header will return a non-nil map for this context.
	QueryWorkflow(context.Context, *ClientQueryWorkflowInput) (converter.EncodedValue, error)

	mustEmbedClientOutboundInterceptorBase()
}

// ClientExecuteWorkflowInput is the input to
// ClientOutboundInterceptor.ExecuteWorkflow.
type ClientExecuteWorkflowInput struct {
	Options      *StartWorkflowOptions
	WorkflowType string
	Args         []interface{}
}

// ClientSignalWorkflowInput is the input to
// ClientOutboundInterceptor.SignalWorkflow.
type ClientSignalWorkflowInput struct {
	WorkflowID string
	RunID      string
	SignalName string
	Arg        interface{}
}

// ClientSignalWithStartWorkflowInput is the input to
// ClientOutboundInterceptor.SignalWithStartWorkflow.
type ClientSignalWithStartWorkflowInput struct {
	SignalName   string
	SignalArg    interface{}
	Options      *StartWorkflowOptions
	WorkflowType string
	Args         []interface{}
}

// ClientCancelWorkflowInput is the input to
// ClientOutboundInterceptor.CancelWorkflow.
type ClientCancelWorkflowInput struct {
	WorkflowID string
	RunID      string
}

// ClientTerminateWorkflowInput is the input to
// ClientOutboundInterceptor.TerminateWorkflow.
type ClientTerminateWorkflowInput struct {
	WorkflowID string
	RunID      string
	Reason     string
	Details    []interface{}
}

// ClientQueryWorkflowInput is the input to
// ClientOutboundInterceptor.QueryWorkflow.
type ClientQueryWorkflowInput struct {
	WorkflowID string
	RunID      string
	QueryType  string
	Args       []interface{}
}
