package phase

import (
	"fmt"
	"strconv"
	"sync"

	"github.com/tidbcloud/devops/entity"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
)

type workflowPhaseMeta struct {
	phase         entity.RemediationTicketPhase
	label         string
	isOptional    bool
	isFinalized   bool
	requiresInput bool
	autoAdvance   bool
}

type PhaseMetadata struct {
	Phase         entity.RemediationTicketPhase
	Label         string
	IsOptional    bool
	IsFinalized   bool
	RequiresInput bool
	AutoAdvance   bool
}

var (
	workflowMu sync.RWMutex

	defaultRemediationWorkflowDefinition = []workflowPhaseMeta{
		{phase: entity.RemediationTicketPhaseCreated, label: "Creator"},
		{phase: entity.RemediationTicketPhaseRegionTSELeader, label: "Region TSE Leader"},
		{phase: entity.RemediationTicketPhaseRDGSLAOwner, label: "RDG SLA Owner"},
		{phase: entity.RemediationTicketPhaseRDGFinalDecisionMaker, label: "RDG Decision Maker"},
		{phase: entity.RemediationTicketPhaseGlobalDealDesk, label: "Global Deal Desk"},
		{phase: entity.RemediationTicketPhaseCommercialOperation, label: "Commercial Operation", isOptional: true},
		{phase: entity.RemediationTicketPhaseRegionalGM, label: "Regional GM"},
		{phase: entity.RemediationTicketPhaseSucceed, label: "Succeed", isFinalized: true},
		{phase: entity.RemediationTicketPhaseClosed, label: "Closed", isFinalized: true},
	}

	remediationWorkflowDefinition = cloneWorkflowDefinition(defaultRemediationWorkflowDefinition)
)

func ConfigureWorkflow(conf config.RemediationWorkflowConfig) error {
	workflowMu.Lock()
	defer workflowMu.Unlock()

	definition, err := buildWorkflowDefinition(conf)
	if err != nil {
		return err
	}
	remediationWorkflowDefinition = definition
	return nil
}

func OrderedPhases() []entity.RemediationTicketPhase {
	workflowMu.RLock()
	defer workflowMu.RUnlock()

	phases := make([]entity.RemediationTicketPhase, 0, len(remediationWorkflowDefinition))
	for _, meta := range remediationWorkflowDefinition {
		phases = append(phases, meta.phase)
	}
	return phases
}

func PhaseLabel(phase entity.RemediationTicketPhase) string {
	workflowMu.RLock()
	defer workflowMu.RUnlock()

	for _, meta := range remediationWorkflowDefinition {
		if meta.phase == phase {
			return meta.label
		}
	}
	return string(phase)
}

func PhaseIsOptional(phase entity.RemediationTicketPhase) bool {
	workflowMu.RLock()
	defer workflowMu.RUnlock()

	for _, meta := range remediationWorkflowDefinition {
		if meta.phase == phase {
			return meta.isOptional
		}
	}
	return false
}

func Metadata(phase entity.RemediationTicketPhase) PhaseMetadata {
	workflowMu.RLock()
	defer workflowMu.RUnlock()

	for _, meta := range remediationWorkflowDefinition {
		if meta.phase == phase {
			return phaseMetadataFromWorkflowMeta(meta)
		}
	}
	return PhaseMetadata{
		Phase: phase,
		Label: string(phase),
	}
}

func WorkflowLength() int {
	workflowMu.RLock()
	defer workflowMu.RUnlock()

	return len(remediationWorkflowDefinition)
}

func ResolveNextPhase(current entity.RemediationTicketPhase, ticket *entity.RemediationTicket) (entity.RemediationTicketPhase, error) {
	switch current {
	case entity.RemediationTicketPhaseCreated:
		return phaseAfter(current)
	case entity.RemediationTicketPhaseRegionTSELeader:
		return phaseAfter(current)
	case entity.RemediationTicketPhaseRDGSLAOwner:
		return phaseAfter(current)
	case entity.RemediationTicketPhaseGlobalDealDesk:
		return phaseAfter(current)
	case entity.RemediationTicketPhaseRDGFinalDecisionMaker:
		if ticket == nil {
			return "", fmt.Errorf("ticket is nil when resolving next phase for %s", current)
		}
		floatExtraPercent, err := strconv.ParseFloat(ticket.ExtraPercent, 32)
		if err != nil {
			return "", fmt.Errorf("parse extra percent %s failed: %w", ticket.ExtraPercent, err)
		}
		if floatExtraPercent >= 5.0 && workflowContains(entity.RemediationTicketPhaseCommercialOperation) {
			return entity.RemediationTicketPhaseCommercialOperation, nil
		}
		if workflowContains(entity.RemediationTicketPhaseRegionalGM) {
			return entity.RemediationTicketPhaseRegionalGM, nil
		}
		return phaseAfter(current)
	case entity.RemediationTicketPhaseCommercialOperation:
		return phaseAfter(current)
	case entity.RemediationTicketPhaseRegionalGM:
		return phaseAfter(current)
	default:
		return "", fmt.Errorf("no next phase defined for %s", current)
	}
}

func buildWorkflowDefinition(conf config.RemediationWorkflowConfig) ([]workflowPhaseMeta, error) {
	if err := validateWorkflowConfig(conf); err != nil {
		return nil, err
	}

	if len(conf.Default.Phases) == 0 {
		return withPhaseHandlerMetadata(cloneWorkflowDefinition(defaultRemediationWorkflowDefinition)), nil
	}

	defaultMetaByPhase := make(map[entity.RemediationTicketPhase]workflowPhaseMeta, len(defaultRemediationWorkflowDefinition))
	for _, meta := range defaultRemediationWorkflowDefinition {
		defaultMetaByPhase[meta.phase] = meta
	}

	labelOverrides := make(map[entity.RemediationTicketPhase]string, len(conf.Default.Labels))
	for phase, label := range conf.Default.Labels {
		labelOverrides[phase] = label
	}

	optionalSet := make(map[entity.RemediationTicketPhase]struct{}, len(conf.Default.OptionalPhases))
	for _, phase := range conf.Default.OptionalPhases {
		optionalSet[phase] = struct{}{}
	}

	definition := make([]workflowPhaseMeta, 0, len(conf.Default.Phases))
	for _, phaseName := range conf.Default.Phases {
		phase := entity.RemediationTicketPhase(phaseName)
		meta, ok := defaultMetaByPhase[phase]
		if !ok {
			meta = workflowPhaseMeta{phase: phase, label: string(phase)}
		}
		if label, ok := labelOverrides[phase]; ok && label != "" {
			meta.label = label
		}
		if _, ok := optionalSet[phase]; ok {
			meta.isOptional = true
		} else {
			meta.isOptional = false
		}
		definition = append(definition, meta)
	}

	return withPhaseHandlerMetadata(definition), nil
}

func phaseAfter(current entity.RemediationTicketPhase) (entity.RemediationTicketPhase, error) {
	workflowMu.RLock()
	defer workflowMu.RUnlock()

	for idx, meta := range remediationWorkflowDefinition {
		if meta.phase != current {
			continue
		}
		for nextIdx := idx + 1; nextIdx < len(remediationWorkflowDefinition); nextIdx++ {
			next := remediationWorkflowDefinition[nextIdx]
			if next.phase == entity.RemediationTicketPhaseClosed {
				continue
			}
			return next.phase, nil
		}
		break
	}

	return "", fmt.Errorf("no next phase defined after %s", current)
}

func workflowContains(phase entity.RemediationTicketPhase) bool {
	workflowMu.RLock()
	defer workflowMu.RUnlock()

	for _, meta := range remediationWorkflowDefinition {
		if meta.phase == phase {
			return true
		}
	}
	return false
}

func cloneWorkflowDefinition(src []workflowPhaseMeta) []workflowPhaseMeta {
	dst := make([]workflowPhaseMeta, len(src))
	copy(dst, src)
	return dst
}

func phaseMetadataFromWorkflowMeta(meta workflowPhaseMeta) PhaseMetadata {
	return PhaseMetadata{
		Phase:         meta.phase,
		Label:         meta.label,
		IsOptional:    meta.isOptional,
		IsFinalized:   meta.isFinalized,
		RequiresInput: meta.requiresInput,
		AutoAdvance:   meta.autoAdvance,
	}
}

func refreshWorkflowDefinitionPhaseMetadata() {
	workflowMu.Lock()
	defer workflowMu.Unlock()

	defaultRemediationWorkflowDefinition = withPhaseHandlerMetadata(defaultRemediationWorkflowDefinition)
	remediationWorkflowDefinition = withPhaseHandlerMetadata(remediationWorkflowDefinition)
}

func withPhaseHandlerMetadata(definition []workflowPhaseMeta) []workflowPhaseMeta {
	for idx := range definition {
		phaseHandler, err := GetPhaseByName(definition[idx].phase)
		if err != nil {
			continue
		}
		definition[idx].isFinalized = phaseHandler.IsFinalized()
		definition[idx].requiresInput = phaseHandler.RequiresInput()
		definition[idx].autoAdvance = phaseHandler.AutoAdvance()
	}
	return definition
}

func validateWorkflowConfig(conf config.RemediationWorkflowConfig) error {
	return validateWorkflowDefinition(conf.Default)
}

func validateWorkflowDefinition(conf config.RemediationWorkflowDefinitionConfig) error {
	if len(conf.Phases) == 0 {
		return nil
	}

	validPhases := container.NewSetFromSlice(entity.RemediationTicketValidPhases)
	validPhases.Insert(entity.RemediationTicketPhaseClosed)

	seenPhases := container.NewSet[entity.RemediationTicketPhase]()
	for idx, phaseName := range conf.Phases {
		phase := entity.RemediationTicketPhase(phaseName)
		if !validPhases.Has(phase) {
			return fmt.Errorf("unknown phase %q in workflow.default.phases", phase)
		}
		if seenPhases.Has(phase) {
			return fmt.Errorf("duplicate phase %q in workflow.default.phases", phase)
		}
		seenPhases.Insert(phase)
		if idx == 0 && phase != entity.RemediationTicketPhaseCreated {
			return fmt.Errorf("workflow.default.phases must start with %q", entity.RemediationTicketPhaseCreated)
		}
	}

	if !seenPhases.Has(entity.RemediationTicketPhaseCreated) {
		return fmt.Errorf("workflow.default.phases must include %q", entity.RemediationTicketPhaseCreated)
	}
	if !seenPhases.Has(entity.RemediationTicketPhaseSucceed) {
		return fmt.Errorf("workflow.default.phases must include %q", entity.RemediationTicketPhaseSucceed)
	}
	if !seenPhases.Has(entity.RemediationTicketPhaseClosed) {
		return fmt.Errorf("workflow.default.phases must include %q", entity.RemediationTicketPhaseClosed)
	}

	if len(conf.Phases) < 2 ||
		entity.RemediationTicketPhase(conf.Phases[len(conf.Phases)-2]) != entity.RemediationTicketPhaseSucceed ||
		entity.RemediationTicketPhase(conf.Phases[len(conf.Phases)-1]) != entity.RemediationTicketPhaseClosed {
		return fmt.Errorf("workflow.default.phases must end with %q, %q", entity.RemediationTicketPhaseSucceed, entity.RemediationTicketPhaseClosed)
	}

	for phaseName := range conf.Labels {
		phase := entity.RemediationTicketPhase(phaseName)
		if !validPhases.Has(phase) {
			return fmt.Errorf("unknown phase %q in workflow.default.labels", phase)
		}
	}

	for _, phaseName := range conf.OptionalPhases {
		phase := entity.RemediationTicketPhase(phaseName)
		if !seenPhases.Has(phase) {
			return fmt.Errorf("optional phase %q is not present in workflow.default.phases", phase)
		}
	}

	return nil
}
