package dag

import (
	"fmt"
	"strings"
	"sync"

	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/base"
)

type DagAction struct {
	Action     actions.Action     `json:"-"`
	PresetArgs actions.ActionArgs `json:"presetArgs,omitempty"`
}

func NewDagAction(action actions.Action, presetArgs actions.ActionArgs) *DagAction {
	if presetArgs == nil {
		presetArgs = make(actions.ActionArgs)
	}
	return &DagAction{
		Action:     action,
		PresetArgs: presetArgs,
	}
}

func (da *DagAction) Clone() *DagAction {
	return &DagAction{
		Action:     da.Action,
		PresetArgs: da.PresetArgs.Clone(),
	}
}

func (da *DagAction) CloneableClone() base.Cloneable {
	return da.Clone()
}

func (da *DagAction) Id() string {
	argId := da.PresetArgs.Id()
	if argId == "" {
		return da.Action.Name()
	}
	return fmt.Sprintf("%v+%v", da.Action.Name(), argId)
}

func (da *DagAction) String() string {
	return da.Id()
}

type DagActions []*DagAction

func (das DagActions) Clone() DagActions {
	var clone DagActions
	for _, it := range das {
		clone = append(clone, it.Clone())
	}
	return clone
}

func (das DagActions) CloneableClone() base.Cloneable {
	return das.Clone()
}

func (das DagActions) String() string {
	str := "["
	if len(das) == 0 {
		str += "(empty)"
	} else {
		for _, da := range das {
			str += da.String() + ","
		}
		str = strings.TrimRight(str, ",")
	}
	str += "]"
	return str
}

func (da *DagAction) Invoke(args actions.ActionArgs) error {
	mergedArgs := actions.MergeActionArgs(args, da.PresetArgs)
	return da.Action.Invoke(mergedArgs)
}

type DagWrapNodeType int

const (
	DagWrapNodeTypeNode   DagWrapNodeType = 1
	DagWrapNodeTypeAction DagWrapNodeType = 2
)

type DagWrapNode struct {
	Type      DagWrapNodeType `json:"type,omitempty"`
	NodeName  string          `json:"nodeName,omitempty"`
	DagAction *DagAction      `json:"dagAction,omitempty"`
}

func (dw *DagWrapNode) Name() string {
	switch dw.Type {
	case DagWrapNodeTypeNode:
		return dw.NodeName
	case DagWrapNodeTypeAction:
		return dw.DagAction.Action.Name()
	default:
		return fmt.Sprintf("DagWrapNode.Name: unknown DagWrapNodeType `%v`", dw.Type)
	}
}

func (dw *DagWrapNode) Clone() *DagWrapNode {
	switch dw.Type {
	case DagWrapNodeTypeNode:
		return &DagWrapNode{
			Type:     dw.Type,
			NodeName: dw.NodeName,
		}
	case DagWrapNodeTypeAction:
		return &DagWrapNode{
			Type:      dw.Type,
			DagAction: dw.DagAction.Clone(),
		}
	default:
		return nil
	}
}

func (dw *DagWrapNode) checkLoop(owner Dag, parentPath []string) bool {
	switch dw.Type {
	case DagWrapNodeTypeNode:
		node := owner.Get(dw.NodeName)
		if node == nil {
			return false
		}
		return node.checkLoop(parentPath)
	case DagWrapNodeTypeAction:
		name := dw.DagAction.Action.Name()
		for _, parentName := range parentPath {
			if name == parentName {
				return true
			}
		}
		return false
	default:
		return false
	}
}

func (dw *DagWrapNode) IsDone(owner Dag, isDoneFunc IsDoneFunc) (bool, error) {
	switch dw.Type {
	case DagWrapNodeTypeNode:
		node := owner.Get(dw.NodeName)
		if node == nil {
			return true, nil
		}
		return node.IsAllDone(isDoneFunc)
	case DagWrapNodeTypeAction:
		return isDoneFunc(dw.DagAction)
	default:
		return false, fmt.Errorf("DagWrapNode.Name: unknown DagWrapNodeType `%v`", dw.Type)
	}
}

type DagNode struct {
	Name      string                  `json:"name,omitempty"`
	DagAction *DagAction              `json:"dagAction,omitempty"`
	Children  map[string]*DagWrapNode `json:"children,omitempty"`
	owner     Dag                     `json:"-"`
}

func NewDagNode(name string, owner Dag) *DagNode {
	return &DagNode{
		Name:     name,
		Children: make(map[string]*DagWrapNode),
		owner:    owner,
	}
}

func (dn *DagNode) String() string {
	str := dn.Name
	if len(dn.Children) != 0 {
		str += "["
		for name, child := range dn.Children {
			childName := child.Name()
			if childName == name {
				str += fmt.Sprintf("%v,", name)
			} else {
				str += fmt.Sprintf("%v:%v,", name, childName)
			}
		}
		str = strings.TrimRight(str, ",")
		str += "]"
	}
	if dn.DagAction != nil {
		str += " Action:" + dn.DagAction.Id()
	}
	return str
}

func (dn *DagNode) AddDependNode(name string) (node *DagNode, err error) {
	if dn.Children[name] != nil {
		return nil, fmt.Errorf("DagNode.AddDependNode: child `%v` already existed in children of `%v`", name, dn.Name)
	}
	child := &DagWrapNode{
		Type:     DagWrapNodeTypeNode,
		NodeName: name,
	}
	dn.Children[name] = child
	// make sure the depend node exists in the Dag
	return dn.owner.NewOrGetNode(name), nil
}

func (dn *DagNode) SafeAddDependNode(name string) *DagNode {
	node, err := dn.AddDependNode(name)
	if err != nil {
		panic(err)
	}
	return node
}

func (dn *DagNode) SafeAddDependNodes(names ...string) {
	for _, name := range names {
		dn.SafeAddDependNode(name)
	}
}

func (dn *DagNode) AssignAction(action actions.Action, presetArgs actions.ActionArgs) error {
	if action.Name() != dn.Name {
		return fmt.Errorf("DagNode.AssignAction: action name `%v` is not equal to node name `%v` (could be allowed but we don't for now)", action.Name(), dn.Name)
	}
	if dn.DagAction != nil {
		return fmt.Errorf("DagNode.AssignAction: action already existed for node `%v` (could be allowed but we don't for now)", dn.Name)
	}
	dn.DagAction = NewDagAction(action, presetArgs)
	return nil
}

func (dn *DagNode) Clone(newOwner Dag) *DagNode {
	clone := &DagNode{
		Name:     dn.Name,
		Children: make(map[string]*DagWrapNode),
		owner:    newOwner,
	}
	if dn.DagAction != nil {
		clone.DagAction = dn.DagAction.Clone()
	}
	for name, child := range dn.Children {
		clone.Children[name] = child.Clone()
	}
	return clone
}

func (dn *DagNode) checkLoop(parentPath []string) bool {
	for _, parentName := range parentPath {
		if dn.Name == parentName {
			return true
		}
	}
	currentPath := append(parentPath, dn.Name) //nolint:gocritic // need a new var name
	for _, child := range dn.Children {
		looped := child.checkLoop(dn.owner, currentPath)
		if looped {
			return true
		}
	}
	return false
}

func (dn *DagNode) Iterate(fn func(name string, node *DagNode) error) error {
	encounteredNodes := map[string]bool{
		dn.Name: true,
	}
	return dn.iterate(fn, encounteredNodes)
}

func (dn *DagNode) IsAllDone(isDoneFunc IsDoneFunc) (bool, error) {
	if dn.DagAction != nil {
		done, err := isDoneFunc(dn.DagAction)
		if err != nil {
			return false, err
		}
		if !done {
			return false, nil
		}
	}
	return dn.IsChildrenAllDone(isDoneFunc)
}

func (dn *DagNode) IsChildrenAllDone(isDoneFunc IsDoneFunc) (bool, error) {
	for _, child := range dn.Children {
		done, err := child.IsDone(dn.owner, isDoneFunc)
		if err != nil {
			return false, err
		}
		if !done {
			return false, nil
		}
	}
	return true, nil
}

func (dn *DagNode) iterate(fn func(name string, node *DagNode) error, encounteredNodes map[string]bool) error {
	err := fn(dn.Name, dn)
	if err != nil {
		return err
	}
	for _, child := range dn.Children {
		name := child.Name()
		encounteredNodes[name] = true
		node := dn.owner.Get(name)
		if node == nil {
			err = fn(name, node)
		} else {
			err = node.iterate(fn, encounteredNodes)
		}
		if err != nil {
			return err
		}
	}
	return nil
}

type Dag map[string]*DagNode

func NewDag() Dag {
	return make(Dag)
}

func (dag Dag) String() string {
	str := fmt.Sprintf("Dag{cnt:%v", len(dag))
	if len(dag) != 0 {
		for _, node := range dag {
			str += fmt.Sprintf(" {%v}", node)
		}
	}
	str += "}"
	return str
}

func (dag *Dag) GetAndCloneDagByRoot(rootNodeName string) Dag {
	root := dag.Get(rootNodeName)
	if root == nil {
		return nil
	}
	newDag := NewDag()
	err := root.Iterate(func(name string, node *DagNode) error {
		if newDag.Get(name) != nil {
			return nil
		}
		return newDag.AddNode(node.Clone(newDag))
	})
	if err != nil {
		panic(fmt.Errorf("SafeDag.GetAndCloneDagByRoot: meet error, should never happend: %w", err))
	}
	return newDag
}

func (dag Dag) Clone() Dag {
	newDag := NewDag()
	for _, node := range dag {
		err := newDag.AddNode(node.Clone(newDag))
		if err != nil {
			panic(fmt.Errorf("Dag.Clone: meet error, should never happend: %w", err))
		}
	}
	return newDag
}

func (dag Dag) NewNode(name string) (*DagNode, error) {
	if dag.Get(name) != nil {
		return nil, fmt.Errorf("Dag.NewNode: node `%v` duplicated", name)
	}
	node := NewDagNode(name, dag)
	dag[name] = node
	return node, nil
}

func (dag Dag) NewOrGetNode(name string) *DagNode {
	node := dag.Get(name)
	if node != nil {
		return node
	}
	node = NewDagNode(name, dag)
	dag[name] = node
	return node
}

func (dag Dag) SafeNewNode(name string) *DagNode {
	node, err := dag.NewNode(name)
	if err != nil {
		panic(err)
	}
	return node
}

func (dag Dag) AddNode(node *DagNode) error {
	name := node.Name
	if dag.Get(name) != nil {
		return fmt.Errorf("Dag.AddNode: node `%v` duplicated", name)
	}
	dag[name] = node
	return nil
}

func (dag Dag) Get(name string) *DagNode {
	return dag[name]
}

type IsDoneFunc func(action *DagAction) (bool, error)

func (dag Dag) GetReadyActionTasks(
	rootName string,
	isDoneFunc IsDoneFunc) (result, waitingActions []*DagAction, err error) {

	cache := make(map[*DagAction]bool)
	isDoneFuncWithCache := func(action *DagAction) (bool, error) {
		if cache[action] {
			return true, nil
		}
		isDone, err := isDoneFunc(action)
		if err != nil {
			return false, err
		}
		if isDone {
			cache[action] = isDone
		}
		return isDone, nil
	}
	return dag.getReadyActionTasks(rootName, isDoneFuncWithCache)
}

func (dag Dag) getReadyActionTasks(
	rootName string,
	isDoneFunc IsDoneFunc) (result, waitingActions []*DagAction, err error) {

	root := dag.Get(rootName)
	if root == nil {
		return nil, nil, nil
	}
	err = root.Iterate(func(name string, node *DagNode) error {
		if node == nil || node.DagAction == nil {
			return nil
		}
		done, err := isDoneFunc(node.DagAction)
		if err != nil {
			return err
		}
		if done {
			return nil
		}
		// TODO: this is slow
		childrenAllDone, err := node.IsChildrenAllDone(isDoneFunc)
		if err != nil {
			return err
		}
		if childrenAllDone {
			result = append(result, node.DagAction)
		} else {
			waitingActions = append(waitingActions, node.DagAction)
		}
		return nil
	})
	if err != nil {
		return nil, nil, err
	}
	return result, waitingActions, nil
}

func (dag Dag) CheckLoop(rootName string) bool {
	root := dag.Get(rootName)
	if root == nil {
		return false
	}
	return root.checkLoop([]string{})
}

func (dag Dag) Iterate(rootName string, fn func(name string, node *DagNode) error) error {
	root := dag.Get(rootName)
	if root == nil {
		return fmt.Errorf("Dag.Iterate: root `%v` not found", rootName)
	}
	return root.Iterate(fn)
}

func (dag Dag) GetNotFoundNodes(rootName string) (nodeNames []string) {
	root := dag.Get(rootName)
	if root == nil {
		return nil
	}
	_ = root.Iterate(func(name string, node *DagNode) error {
		if node == nil {
			nodeNames = append(nodeNames, name)
		}
		return nil
	})
	return nodeNames
}

type Plans struct {
	plans  map[string]Dag
	locker sync.Mutex
}

func NewPlans() *Plans {
	return &Plans{
		plans: make(map[string]Dag),
	}
}

func (plans *Plans) Clone() *Plans {
	plans.locker.Lock()
	defer plans.locker.Unlock()
	clonePlans := make(map[string]Dag)
	if plans.plans != nil {
		for name, plan := range plans.plans {
			clonePlans[name] = plan.Clone()
		}
	}
	clone := &Plans{
		plans: clonePlans,
	}
	return clone
}

func (plans *Plans) CloneableClone() base.Cloneable {
	return plans.Clone()
}

func (plans *Plans) String() string {
	plans.locker.Lock()
	defer plans.locker.Unlock()
	var str string
	for name, plan := range plans.plans {
		str += fmt.Sprintf("plan#%v:%v;", name, plan)
	}
	return str
}

func (plans *Plans) RegisterPlan(rootNodeName string, plan Dag) error {
	_, existed := plans.plans[rootNodeName]
	if existed {
		return fmt.Errorf("Plans.RegisterPlan: plan `%s` existed `%v`", rootNodeName, plan.String())
	}
	plans.plans[rootNodeName] = plan
	return nil
}

func (plans *Plans) SafeRegisterPlan(rootNodeName string, plan Dag) {
	err := plans.RegisterPlan(rootNodeName, plan)
	if err != nil {
		panic(err)
	}
}

func (plans *Plans) GetAndCloneDagByRoot(rootNodeName string) Dag {
	plans.locker.Lock()
	defer plans.locker.Unlock()
	plan := plans.plans[rootNodeName]
	if plan != nil {
		plan = plan.GetAndCloneDagByRoot(rootNodeName)
	}
	return plan
}

func (plans *Plans) NewPlanWithRootNode(rootNodeName string) *DagNode {
	plan := NewDag()
	plans.SafeRegisterPlan(rootNodeName, plan)
	rootNode := plan.SafeNewNode(rootNodeName)
	return rootNode
}
