package actions

import (
	"context"
	"fmt"

	"github.com/go-logr/logr"
	userv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/core-server/user/v1"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/infra/clients/billingcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/byocimagesynccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/cliniccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/configcentercli"
	"github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/dataflowcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/dedicatedcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/fadcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/fidcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/githubcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/larkcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/mgmthttpcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/oncallcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/serverlesscli"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/eventbus"
	auditsvc "github.com/tidbcloud/devops/pkg/service/audit"
	cloudconfigapi "github.com/tidbcloud/devops/pkg/service/cloudconfig/api"
	"github.com/tidbcloud/devops/pkg/service/operation/repo"
	"github.com/tidbcloud/devops/pkg/service/project"
	"github.com/tidbcloud/devops/pkg/service/tenant"
	"github.com/tidbcloud/pkgv2/db"
	"google.golang.org/genai"
)

// AdvisoryAction is an optional interface actions may implement to return
// non-blocking advisories before an instance is created.
type AdvisoryAction interface {
	Advisory(ctx context.Context, instance *opent.Instance) ([]opent.Advisory, error)
}

var (
	ActionAlwaysSucceed opent.ActionMetadata = opent.ActionMetadata{
		Name: "AlwaysSucceed",
		Type: opent.ActionTypeAuto,
	}
	ActionAlwaysFail opent.ActionMetadata = opent.ActionMetadata{
		Name: "AlwaysFail",
		Type: opent.ActionTypeAuto,
	}
)

// Components contains all the components that actions needs.
type Components struct {
	Conf            config.OperationServiceConfig
	Logger          logr.Logger
	LarkCli         *larkcli.LarkClient
	UserAPI         userv1.UserServiceClient
	TenantAPI       tenant.TenantServiceAPI
	ProjectAPI      project.ProjectServiceAPI
	CloudConfigAPI  cloudconfigapi.CloudConfigServiceAPI
	CoreSvc         *coresvccli.CoreSvcCli
	ClinicCli       cliniccli.Client
	GeminiCli       *genai.Client
	EventBus        *eventbus.EventBus
	MgmtHTTP        mgmthttpcli.MgmtHTTPClient
	BillingCli      billingcli.Client
	GithubActionCli githubcli.Client
	DataflowCli     *dataflowcli.DataflowCli
	FadCli          fadcli.Client
	FIDCli          fidcli.Client
	BYOCSyncCli     byocimagesynccli.Client
	KernelImageConf config.KernelImageServiceConfig
	AuditService    auditsvc.AuditServiceAPI
	InfraAPICli     dedicatedcli.InfraAPIClient
	ServerlessCli   *serverlesscli.Client
	CfgCenterCli    *configcentercli.Client
	OnCallCli       oncallcli.Client
	DBProvider      db.TransProvider

	OpRepo repo.OperationRepo
}

type ActionResult struct {
	// Result is the result of this action.
	Result opent.ActionStatus
	// Output is the output of this action.
	//
	// It will be saved in the instance action whatever the action is succeeded or failed.
	ActionOutput string
	// InstanceOutputs is the outputs of the instance.
	//
	// It will be saved in the instance whatever the action is succeeded or failed.
	InstanceOutputs map[opent.OutputName]string
}

func NewActionResult(result opent.ActionStatus, actOutput string, insOutputs map[opent.OutputName]string) *ActionResult {
	return &ActionResult{Result: result, ActionOutput: actOutput, InstanceOutputs: insOutputs}
}

func SucceededActionResult(insOutputs map[opent.OutputName]string) *ActionResult {
	return &ActionResult{Result: opent.ActionStatusSucceeded, ActionOutput: "", InstanceOutputs: insOutputs}
}

func RunningActionResult(output string, insOutputs map[opent.OutputName]string) *ActionResult {
	return &ActionResult{Result: opent.ActionStatusRunning, ActionOutput: output, InstanceOutputs: insOutputs}
}

func FailedActionResult(err error) *ActionResult {
	return &ActionResult{Result: opent.ActionStatusFailed, ActionOutput: err.Error()}
}

type Action interface {
	// Metadata returns the metadata of the action.
	Metadata() opent.ActionMetadata
	// PreCheck checks whether params are valid before create a instance.
	PreCheck(ctx context.Context, instance *opent.Instance) error
	// Start the action.
	//
	// If success, return running or succeeded result and nil.
	// If failed, return failed result and nil.
	// If internal error, return nil and error, and the action will be retried later in instance service.
	Start(ctx context.Context, instance *opent.Instance) (*ActionResult, error)
}

// AutoAction means the action is executed by calling the api, and we will
// check the result asynchronously.
type AutoAction interface {
	Action

	// Result check if the action is finished and return its result.
	//
	// If the action is still running, return running result and nil.
	// If the action is failed, return failed result and nil.
	// If error, return unknown result and nil.
	// If internal error, return nil and error, and the action will be retried later in instance service.
	Result(ctx context.Context, instance *opent.Instance) (*ActionResult, error)

	// OnCancel is called when the instance is cancelled.
	//
	// For every action, it will be called by in reverse order.
	//
	// If the action is successfully cancelled, return true and nil, and the status is set to canceled.
	// If the action cannot be cancelled, return false and nil, and the status is unchanged.
	OnCancel(ctx context.Context, instance *opent.Instance) (bool /* canceled */, error)
}

// ManualAction means the action is executed by a person, and the result will be
// marked by the person.
type MaunalAction interface {
	Action

	// OnFinish means this action is finished by a person.
	OnFinish(ctx context.Context, instance *opent.Instance, result opent.ActionStatus, output string) error
}

type (
	BaseAction    struct{}
	AlwaysSucceed struct{}
	AlwaysFail    struct{}
)

func NewAlwaysSucceed(comps *Components) AutoAction {
	return &AlwaysSucceed{}
}

func NewAlwaysFail(comps *Components) AutoAction {
	return &AlwaysFail{}
}

func (b BaseAction) Metadata() opent.ActionMetadata {
	return opent.ActionMetadata{
		Name:             "BaseAction",
		Type:             opent.ActionTypeAuto,
		AllowAnyoneApply: true,
	}
}

func (b BaseAction) PreCheck(ctx context.Context, instance *opent.Instance) error {
	return nil
}

func (b BaseAction) Start(ctx context.Context, instance *opent.Instance) (*ActionResult, error) {
	return SucceededActionResult(nil), nil
}

func (b BaseAction) Result(ctx context.Context, instance *opent.Instance) (*ActionResult, error) {
	return SucceededActionResult(nil), nil
}

func (b BaseAction) OnCancel(ctx context.Context, instance *opent.Instance) (bool, error) {
	return false, nil
}

func (a *AlwaysSucceed) Metadata() opent.ActionMetadata {
	return ActionAlwaysSucceed
}

func (a *AlwaysSucceed) PreCheck(ctx context.Context, instance *opent.Instance) error {
	return nil
}

func (a *AlwaysSucceed) Start(ctx context.Context, instance *opent.Instance) (*ActionResult, error) {
	return SucceededActionResult(nil), nil
}

func (a *AlwaysSucceed) Result(ctx context.Context, instance *opent.Instance) (*ActionResult, error) {
	return SucceededActionResult(nil), nil
}

func (a *AlwaysSucceed) OnCancel(ctx context.Context, instance *opent.Instance) (bool, error) {
	return false, nil
}

func (a *AlwaysFail) Metadata() opent.ActionMetadata {
	return ActionAlwaysFail
}

func (a *AlwaysFail) PreCheck(ctx context.Context, instance *opent.Instance) error {
	return nil
}

func (a *AlwaysFail) Start(ctx context.Context, instance *opent.Instance) (*ActionResult, error) {
	return FailedActionResult(fmt.Errorf("always fail")), nil
}

func (a *AlwaysFail) Result(ctx context.Context, instance *opent.Instance) (*ActionResult, error) {
	return FailedActionResult(fmt.Errorf("always fail")), nil
}

func (a *AlwaysFail) OnCancel(ctx context.Context, instance *opent.Instance) (bool, error) {
	return false, fmt.Errorf("always fail")
}
