package service

import (
	"fmt"
	"io"
	"os"

	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/dag"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/executor"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/storage"
	"github.com/tidbcloud/cluster-service-ng/pkg/template/render"
)

type Service struct {
	executor *executor.Executor
	actions  *actions.ActionLibrary
	output   io.Writer

	// renders    *ActionRenderLibrary,
	// templates  *render.TemplateLibrary,
}

func NewService(stg storage.KvStg, output io.Writer) (*Service, error) {
	// TODO: read templates from CRs
	templateLib := render.NewTemplateLibrary()
	SafeRegisterTemplates(templateLib)

	// TODO: use renders to help loading plans
	// renders := executor.NewActionRenderLibrary()
	// renders.SafeRegister("k8s-action-render:v1", executor.NewK8sActionRenderV1())
	// renders.SafeRegister("builtin-function-action-render:v1", executor.NewBuiltinFunctionActionRenderV1())
	// renders.SafeRegister("call-another-action-render:v1", executor.NewCallAnotherActionRenderV1())

	// TODO: read actions from CRs with renders' help
	// for every CR of action:
	// - action definition: name, args, render-name, render-input(a block of yaml)
	// - get render by name
	// - the render parse the input block, create Action object, register to action-lib
	actionLib := actions.NewActionLibrary()
	SafeRegisterActions(actionLib, templateLib)

	logicPlans := dag.NewPlans()
	SafeRegisterLogicPlans(logicPlans)

	execKvStg := storage.NewPrefixWrapKvStg([]string{"executor"}, stg)
	execStatusStg := storage.NewKvBasedExecStatusStg(execKvStg)
	dagKvStg := storage.NewPrefixWrapKvStg([]string{"dags"}, stg)
	dagStatusStg := storage.NewKvBasedDagStatusStg(dagKvStg)

	exec, err := executor.NewExecutor(actionLib, logicPlans, execStatusStg, dagStatusStg, 999999)
	if err != nil {
		return nil, err
	}
	logger := executor.NewGeneralLogger("example-service", os.Stderr)
	exec.RegisterEventReceiver(logger)
	metrics := executor.NewMetricsRecorder("example-service")
	exec.RegisterEventReceiver(metrics)

	return &Service{
		executor: exec,
		actions:  actionLib,
		output:   output,
	}, nil
}

func (svc *Service) Start() error {
	return svc.executor.Start(executor.ExitPolicyNonStop)
}

func (svc *Service) WaitForAllFinish() error {
	return svc.executor.Stop(executor.ExitPolicyStopOnFinish)
}

func (svc *Service) GetMockK8sAction(name string) *actions.MockK8sAction {
	action := svc.actions.Get(name)
	if action == nil {
		return nil
	}
	return action.(*actions.MockK8sAction)
}

type CreateClusterReq struct {
	ClusterId string
	TiKVCount int
	TraceId   string
}

type CreateClusterResp struct {
	IsDone     bool
	ExecId     string
	Args       actions.ActionArgs
	TaskStatus executor.ActionTaskIdStatus
}

func (resp *CreateClusterResp) Dump(title string, w io.Writer) {
	_, _ = fmt.Fprintf(w, "[CreateClusterResp.Dump]\n\t# %s\n\tIsDone: %v\n\targs: %v\n",
		title,
		resp.IsDone,
		resp.Args)
	resp.TaskStatus.Dump("\t", w)
}

func (svc *Service) CreateCluster(req *CreateClusterReq) (resp *CreateClusterResp, err error) {
	args := actions.ActionArgs{
		createClusterArgKeyClusterId: actions.ActionArgString(req.ClusterId),
		createClusterArgKeyTiKVCount: actions.ActionArgInt(req.TiKVCount),
	}
	execId := req.TraceId
	err = svc.executor.Submit(createClusterPlanName, args, execId)
	if err != nil {
		return nil, err
	}
	return svc.GetCreateClusterStatus(req)
}

func (svc *Service) GetCreateClusterStatus(req *CreateClusterReq) (resp *CreateClusterResp, err error) {
	execIds, err := svc.executor.QueryExecTasks(createClusterPlanName, func(args actions.ActionArgs) (bool, error) {
		sharedPoolId, err := args.GetString(createClusterArgKeyClusterId)
		if err != nil {
			return false, err
		}
		return sharedPoolId == req.ClusterId, nil
	})
	if err != nil {
		return nil, err
	}
	if len(execIds) > 1 {
		return nil, fmt.Errorf("Service.GetCreateClusterStatus: more than one matched CreateCluster of cluster `%v` is running",
			req.ClusterId)
	}
	if len(execIds) == 0 {
		return nil, fmt.Errorf("Service.GetCreateClusterStatus: no matched CreateCluster of cluster `%v` is running",
			req.ClusterId)
	}
	execId := execIds[0]
	status := svc.executor.GetRunningStatus(execId)
	if status == nil {
		return nil, fmt.Errorf("Service.GetCreateClusterStatus: cluster `%v` doesn't have status",
			req.ClusterId)
	}
	return &CreateClusterResp{
		IsDone:     status.ActionsStatus.IsAllDone(true),
		ExecId:     execId,
		Args:       status.ExecTask.Args,
		TaskStatus: status.ActionsStatus,
	}, nil
}

const createClusterPlanName = "create-cluster:v1"
const createClusterArgKeyClusterId = "sharedPoolId"
const createClusterArgKeyTiKVCount = "tikvId"
