package cvt

import (
	"encoding/json"
	"fmt"

	"github.com/tidbcloud/chaos-svc/idl/pbgen/proto/chaos"
	"github.com/tidbcloud/chaos-svc/model"
	"github.com/tidbcloud/chaos-svc/pkg/domain/constant"
	"github.com/tidbcloud/chaos-svc/pkg/domain/entity"
	"github.com/tidbcloud/chaos-svc/pkg/domain/vo"
)

type WorkflowGroup struct {
	Workflow *model.Workflow
	Nodes    map[string]*model.WorkflowNode
}

func WorkflowModel2Entity(workflow *model.Workflow, nodesModel map[string]*model.WorkflowNode) *entity.Workflow {
	nodes := make(map[string]*vo.WorkflowNode, len(nodesModel))
	for _, node := range nodesModel {
		nodes[node.Name] = &vo.WorkflowNode{
			ID:          node.ID.Uint64(),
			Name:        node.Name,
			Status:      constant.WorkflowNodeStatus(node.Status),
			Message:     node.Message,
			CreatedAt:   node.CreatedAt,
			CompletedAt: node.CompletedAt,
		}
	}
	draft := false
	if workflow.Status == model.WorkflowStatusDraft {
		draft = true
	}
	return &entity.Workflow{
		ID:          workflow.ID.Uint64(),
		ProjectID:   workflow.ProjectID.Uint64(),
		ClusterID:   workflow.ClusterID.Uint64(),
		Draft:       draft,
		Status:      WorkflowStatusModel2Pb(workflow.Status),
		Message:     workflow.Message,
		Spec:        WorkflowSpecModel2Entity(workflow.Spec),
		Nodes:       nodes,
		CreatedByID: workflow.CreatedByID.Uint64(),
		CreatedAt:   workflow.CreatedAt,
		StartedAt:   workflow.StartedAt,
		CompletedAt: workflow.CompletedAt,
		DeletedAt:   workflow.DeletedAt,
		ReportLink:  workflow.ReportLink,
		Summary:     workflow.Summary,
		Versioning:  workflow.Versioning,
	}
}

func WorkflowEntity2Model(workflow *entity.Workflow) *WorkflowGroup {
	workflowModel := &model.Workflow{
		Updatable: model.Updatable{
			Base: model.Base{ID: model.ID(workflow.ID)},
		},
		Versioned:   model.Versioned{Versioning: workflow.Versioning},
		ProjectID:   model.ID(workflow.ProjectID),
		ClusterID:   model.ID(workflow.ClusterID),
		Status:      WorkflowStatusPb2Model(workflow.Status),
		Message:     workflow.Message,
		Spec:        WorkflowSpecEntity2Model(workflow.Spec),
		CreatedByID: model.ID(workflow.CreatedByID),
		StartedAt:   workflow.StartedAt,
		CompletedAt: workflow.CompletedAt,
		DeletedAt:   workflow.DeletedAt,
		ReportLink:  workflow.ReportLink,
		Summary:     workflow.Summary,
	}
	nodes := make(map[string]*model.WorkflowNode)
	for _, node := range workflow.Nodes {
		nodes[node.Name] = &model.WorkflowNode{
			Updatable: model.Updatable{
				Base: model.Base{
					ID:        model.ID(node.ID),
					CreatedAt: node.CreatedAt, // the same as `spec.startime` of `ChaosWorkflowNode` cr
				},
			},
			WorkflowID:  model.ID(workflow.ID),
			Name:        node.Name,
			Status:      model.WorkflowNodeStatus(node.Status),
			Message:     node.Message,
			CompletedAt: node.CompletedAt,
		}
	}
	return &WorkflowGroup{
		Workflow: workflowModel,
		Nodes:    nodes,
	}
}

func WorkflowStatusPb2Model(status chaos.ExperimentStatus) model.WorkflowStatus {
	switch status {
	case chaos.ExperimentStatus_EXPERIMENT_STATUS_DRAFT:
		return model.WorkflowStatusDraft
	case chaos.ExperimentStatus_EXPERIMENT_STATUS_RUNNING:
		return model.WorkflowStatusRunning
	case chaos.ExperimentStatus_EXPERIMENT_STATUS_SUCCESS:
		return model.WorkflowStatusSuccess
	case chaos.ExperimentStatus_EXPERIMENT_STATUS_FAILED:
		return model.WorkflowStatusFailed
	case chaos.ExperimentStatus_EXPERIMENT_STATUS_DELETED:
		return model.WorkflowStatusDeleted
	}
	panic(fmt.Sprintf("invalid workflow status: %s", status))
}

func WorkflowStatusModel2Pb(status model.WorkflowStatus) chaos.ExperimentStatus {
	switch status {
	case model.WorkflowStatusDraft:
		return chaos.ExperimentStatus_EXPERIMENT_STATUS_DRAFT
	case model.WorkflowStatusRunning:
		return chaos.ExperimentStatus_EXPERIMENT_STATUS_RUNNING
	case model.WorkflowStatusSuccess:
		return chaos.ExperimentStatus_EXPERIMENT_STATUS_SUCCESS
	case model.WorkflowStatusFailed:
		return chaos.ExperimentStatus_EXPERIMENT_STATUS_FAILED
	case model.WorkflowStatusDeleted:
		return chaos.ExperimentStatus_EXPERIMENT_STATUS_DELETED
	}
	panic(fmt.Sprintf("invalid workflow status: %s", status))
}

func WorkflowSpecModel2Entity(spec string) entity.WorkflowSpec {
	specModel := DecodeWorkflowSpec(spec)
	templates := make(map[string]entity.WorkflowNodeTemplate, len(specModel.Templates))

	for _, each := range specModel.Templates {
		templates[each.Name] = entity.WorkflowNodeTemplate{
			Name:        each.Name,
			Type:        constant.WorkflowTemplateType(each.Type),
			Children:    each.Children,
			EmbedAction: buildEmbedActionEntity(each),
		}
	}
	return entity.WorkflowSpec{
		EntryNodeName: specModel.EntryNodeName,
		Templates:     templates,
	}
}

func buildEmbedActionEntity(node model.WorkflowNodeTemplate) *entity.EmbedAction {
	if node.EmbedAction == nil {
		return nil
	}
	switch node.Type {
	case model.WorkflowTemplateTypePodShutdown:
		return &entity.EmbedAction{
			PodShutdown: &entity.PodShutdownAction{
				Target: chaos.ExperimentTarget{
					Mode:      TargetModeModel2Pb(node.PodShutdown.Target.Mode),
					Component: TargetComponentModel2Pb(node.PodShutdown.Target.Component),
					Name:      &node.PodShutdown.Target.PodNames[0],
				},
			},
		}
	}
	panic(fmt.Sprintf("invalid embed action type: %s", node.Type))
}

func WorkflowSpecEntity2Model(spec entity.WorkflowSpec) string {
	templates := make([]model.WorkflowNodeTemplate, 0, len(spec.Templates))
	for _, each := range spec.Templates {
		templates = append(templates, model.WorkflowNodeTemplate{
			Name:        each.Name,
			Type:        model.WorkflowTemplateType(each.Type),
			Children:    each.Children,
			EmbedAction: buildEmbedActionModel(each),
		})
	}
	return EncodeWorkflowSpec(model.WorkflowSpec{
		EntryNodeName: spec.EntryNodeName,
		Templates:     templates,
	})
}

func buildEmbedActionModel(node entity.WorkflowNodeTemplate) *model.EmbedAction {
	if node.EmbedAction == nil {
		return nil
	}
	name := ""
	if node.EmbedAction.PodShutdown.Target.Name != nil {
		name = *node.EmbedAction.PodShutdown.Target.Name
	}
	switch node.Type {
	case constant.WorkflowTemplateTypePodShutdown:
		return &model.EmbedAction{
			PodShutdown: &model.PodShutdownAction{
				Target: model.ActionTarget{
					Mode:         TargetModePb2Model(node.PodShutdown.Target.Mode),
					Component:    TargetComponentPb2Model(node.PodShutdown.Target.Component),
					PodNames:     []string{name},
					SelectorMode: model.OneMode,
				},
			},
		}
	}
	panic(fmt.Sprintf("invalid embed action type: %s", node.Type))
}

func TargetModePb2Model(mode chaos.TargetMode) model.TargetMode {
	switch mode {
	case chaos.TargetMode_TARGET_MODE_RANDOM:
		return model.TargetModeRandom
	case chaos.TargetMode_TARGET_MODE_SPECIFIED:
		return model.TargetModeSpecified
	}
	panic(fmt.Sprintf("invalid target mode: %s", mode))
}

func TargetModeModel2Pb(mode model.TargetMode) chaos.TargetMode {
	switch mode {
	case model.TargetModeRandom:
		return chaos.TargetMode_TARGET_MODE_RANDOM
	case model.TargetModeSpecified:
		return chaos.TargetMode_TARGET_MODE_SPECIFIED
	}
	panic(fmt.Sprintf("invalid target mode: %s", mode))
}

func TargetComponentPb2Model(component chaos.TargetComponent) model.TargetComponentType {
	switch component {
	case chaos.TargetComponent_TARGET_COMPONENT_TIDB:
		return model.TargetComponentTypeTiDB
	case chaos.TargetComponent_TARGET_COMPONENT_TIKV:
		return model.TargetComponentTypeTiKV
	case chaos.TargetComponent_TARGET_COMPONENT_PD:
		return model.TargetComponentTypePD
	}
	panic(fmt.Sprintf("invalid target component: %s", component))
}

func TargetComponentModel2Pb(component model.TargetComponentType) chaos.TargetComponent {
	switch component {
	case model.TargetComponentTypeTiDB:
		return chaos.TargetComponent_TARGET_COMPONENT_TIDB
	case model.TargetComponentTypeTiKV:
		return chaos.TargetComponent_TARGET_COMPONENT_TIKV
	case model.TargetComponentTypePD:
		return chaos.TargetComponent_TARGET_COMPONENT_PD
	}
	panic(fmt.Sprintf("invalid target component: %s", component))
}

func DecodeWorkflowSpec(workflowSpec string) model.WorkflowSpec {
	var spec model.WorkflowSpec
	if err := json.Unmarshal([]byte(workflowSpec), &spec); err != nil {
		panic(fmt.Sprintf("decode workflow spec failed: %s", err))
	}
	return spec
}

func EncodeWorkflowSpec(workflowSpec model.WorkflowSpec) string {
	spec, err := json.Marshal(workflowSpec)
	if err != nil {
		panic(fmt.Sprintf("encode workflow spec failed: %s", err))
	}
	return string(spec)
}
