package operation

import (
	"encoding/json"
	"fmt"
	"strconv"

	maintent "github.com/tidbcloud/devops/entity/maintenance"
)

type (
	InstancePhase string
)

// Enums
// Update value list `XXXValues` when update a enum
const (
	InstancePhaseDraft     InstancePhase = "draft"
	InstancePhaseCreated   InstancePhase = "created"
	InstancePhaseApproving InstancePhase = "approving"
	InstancePhaseWithdrawn InstancePhase = "withdrawn"
	InstancePhasePending   InstancePhase = "pending"
	InstancePhaseRunning   InstancePhase = "running"
	InstancePhaseSucceeded InstancePhase = "succeeded"
	InstancePhaseFailed    InstancePhase = "failed"
	InstancePhaseCanceled  InstancePhase = "canceled"
)

var (
	InstancePhaseValues = []InstancePhase{
		InstancePhaseDraft, InstancePhaseCreated, InstancePhaseApproving, InstancePhaseWithdrawn,
		InstancePhasePending, InstancePhaseRunning,
		InstancePhaseSucceeded, InstancePhaseFailed, InstancePhaseCanceled,
	}
	InstancePhaseFinishedValues = []InstancePhase{
		InstancePhaseSucceeded, InstancePhaseFailed, InstancePhaseCanceled,
	}
	InstancePhaseCanNotRetryValues = []InstancePhase{
		InstancePhaseSucceeded, InstancePhaseCanceled,
	}
)

type InstanceAction struct {
	ID              uint64            `json:"id"`
	Order           ActionOrder       `json:"order"`
	Name            ActionName        `json:"name"`
	Type            ActionType        `json:"type"`
	StartPolicy     ActionStartPolicy `json:"start_policy"`
	StartScheduleAt *int64            `json:"start_schedule_at,omitempty"` // schedule time if start policy is `schedule`

	StartedAt  *int64       `json:"started_at"`
	FinishedAt *int64       `json:"finished_at"`
	Status     ActionStatus `json:"status"`
	Output     string       `json:"output"`
}

type Instance struct {
	ID        uint64          `json:"id"`
	Type      OperationTarget `json:"type"`
	Operation string          `json:"operation"`
	Reason    string          `json:"reason"`
	// Applicant is the user email who create the instance.
	Applicant string `json:"applicant"` // email
	// OperatorEntity is the real operation entity who create the instance.
	OperatorEntity string `json:"operator_entity"`
	// Params is a map used to provide parameters for actions.
	Params map[ParamName]string `json:"params"`
	// Outputs is a map used to store some contexts or results for actions.
	Outputs map[OutputName]string `json:"outputs"`

	ExecutionWindowID *uint64                      `json:"execution_window_id"`
	ExecutionWindow   *maintent.OpsExecutionWindow `json:"execution_window"` // only set in get/list API

	// status
	Phase      InstancePhase `json:"phase"`
	ApprovalID *uint64       `json:"approval_id"`
	CreatedAt  int64         `json:"created_at"`
	ClosedAt   *int64        `json:"closed_at"`

	Actions []*InstanceAction `json:"actions"`
}

func (i Instance) GetAction(order ActionOrder) *InstanceAction {
	for _, action := range i.Actions {
		if action.Order == order {
			return action
		}
	}
	return nil
}

func (i Instance) GetParam(name ParamName) (string, bool) {
	v, ok := i.Params[name]
	return v, ok
}

func (i Instance) GetIntParam(name ParamName) (int64, error) {
	v, ok := i.Params[name]
	if !ok {
		return 0, fmt.Errorf("param %s is not found", name)
	}
	value, err := strconv.ParseInt(v, 10, 64)
	if err != nil {
		return 0, fmt.Errorf("param %s is not a valid integer: %s", name, v)
	}
	return value, nil
}

func (i Instance) GetUintParam(name ParamName) (uint64, error) {
	v, ok := i.Params[name]
	if !ok {
		return 0, fmt.Errorf("param %s is not found", name)
	}
	value, err := strconv.ParseUint(v, 10, 64)
	if err != nil {
		return 0, fmt.Errorf("param %s is not a valid unsigned integer: %s", name, v)
	}
	return value, nil
}

// GetMapParam parses a JSON-encoded map[string]string parameter.
func (i Instance) GetMapParam(name ParamName) (map[string]string, error) {
	v, ok := i.Params[name]
	if !ok {
		return nil, fmt.Errorf("param %s is not found", name)
	}
	if v == "" {
		return nil, nil
	}
	m := map[string]string{}
	if err := json.Unmarshal([]byte(v), &m); err != nil {
		return nil, fmt.Errorf("param %s is not a valid map: %s", name, v)
	}
	return m, nil
}

func (i Instance) GetOutput(name OutputName) (string, bool) {
	v, ok := i.Outputs[name]
	return v, ok
}

func (i Instance) ExistsTargetID() (string /*target id*/, bool) {
	paramName, ok := OperationTargetIDParamNames[i.Type]
	if !ok {
		return "", false
	}
	id, exist := i.GetParam(paramName)
	if !exist || id == "" {
		return "", false
	}
	return id, true
}
