package config

import (
	"fmt"
	"slices"
	"strings"

	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/api"

	ccproto "github.com/tidbcloud/config-center/proto/config_center"
)

var (
	reloadableConfigTypes = map[string]struct{}{
		ccproto.DEDICATED_TIDB_CONFIG_TIDB:            {},
		ccproto.DEDICATED_TIDB_CONFIG_TIKV:            {},
		ccproto.DEDICATED_TIDB_CONFIG_PD:              {},
		ccproto.DEDICATED_TIDB_CONFIG_TIFLASH:         {},
		ccproto.DEDICATED_TIDB_CONFIG_TIFLASH_LEARNER: {},
	}
)

type ParameterApplyPolicyFileResult struct {
	ModifiedParams   container.Set[string]
	ReloadableParams container.Set[string]
	MissAttrParams   container.Set[string]

	Result ccent.ParameterApplyPolicy
	Reason string
}

type ParameterApplyPolicyEvaluator struct {
	FileResult map[string]*ParameterApplyPolicyFileResult
	ChangeType api.ChangeType
}

func NewParameterApplyPolicyEvaluator(changeType api.ChangeType) *ParameterApplyPolicyEvaluator {
	return &ParameterApplyPolicyEvaluator{
		FileResult: make(map[string]*ParameterApplyPolicyFileResult),
		ChangeType: changeType,
	}
}

func (e *ParameterApplyPolicyEvaluator) EvaluateFile(filename string, configType string, modifiedParams []ccent.ChangedParameter, attrs map[string]ccent.ParameterAttribute) {
	fileResult := e.FileResult[filename]
	if fileResult == nil {
		e.FileResult[filename] = &ParameterApplyPolicyFileResult{
			Result:           ccent.ParameterApplyPolicyNone,
			ModifiedParams:   container.NewSet[string](),
			ReloadableParams: container.NewSet[string](),
			MissAttrParams:   container.NewSet[string](),
		}
		fileResult = e.FileResult[filename]
	}

	// record informations
	for _, item := range modifiedParams {
		fileResult.ModifiedParams.Insert(item.Name)

		attr, exist := attrs[item.Name]
		if !exist {
			fileResult.MissAttrParams.Insert(item.Name)
			continue
		}

		if attr.ApplyPolicy == ccent.ParameterApplyPolicyHotReload {
			fileResult.ReloadableParams.Insert(item.Name)
		}
	}

	// evaluate file level
	if _, exist := reloadableConfigTypes[configType]; !exist {
		fileResult.Result = ccent.ParameterApplyPolicyRestart
		fileResult.Reason = "file is not reloadable"
		return
	}
	if fileResult.ModifiedParams.Len() == fileResult.ReloadableParams.Len() {
		fileResult.Result = ccent.ParameterApplyPolicyHotReload
		fileResult.Reason = "all parameters are reloadable"
		return
	}

	fileResult.Result = ccent.ParameterApplyPolicyRestart
	if fileResult.MissAttrParams.Len() > 0 {
		fileResult.Reason = fmt.Sprintf("some parameters are missing in config dict: [%s]", strings.Join(fileResult.MissAttrParams.Slice(), ","))
	} else {
		diffParams := fileResult.ModifiedParams.Difference(fileResult.MissAttrParams).Difference(fileResult.ReloadableParams)
		fileResult.Reason = fmt.Sprintf("some parameters are unreloadable: [%s]", strings.Join(diffParams.Slice(), ","))
	}

}

func (e *ParameterApplyPolicyEvaluator) Result() ([]ccent.ParameterApplyPolicy, string) {
	reasons := []string{}
	results := container.NewSetFromSlice([]ccent.ParameterApplyPolicy{
		ccent.ParameterApplyPolicyRestart,
		ccent.ParameterApplyPolicyEnsureUpdateToTC,
	})

	switch e.ChangeType {
	case api.ChangeTypeDm:
		results = container.NewSetFromSlice([]ccent.ParameterApplyPolicy{
			ccent.ParameterApplyPolicyRestart,
			ccent.ParameterApplyPolicyNewInstancesOnly,
		})
	case api.ChangeTypeLogicalCluster, api.ChangeTypeSharedPool:
		results = container.NewSetFromSlice([]ccent.ParameterApplyPolicy{
			ccent.ParameterApplyPolicyHotReload,
			ccent.ParameterApplyPolicyRestart,
			ccent.ParameterApplyPolicyForceRestart,
		})
	}

	{
		if len(e.FileResult) != 0 {
			allHotReload := true
			for filename, fileResult := range e.FileResult {
				if fileResult.Result == ccent.ParameterApplyPolicyRestart {
					allHotReload = false
				}
				reasons = append(reasons, fmt.Sprintf("<%s>: %s", filename, fileResult.Reason))
			}
			if allHotReload {
				results.Insert(ccent.ParameterApplyPolicyHotReload)
			}
		}
	}

	policies := results.Slice()
	slices.SortFunc(policies, func(a, b ccent.ParameterApplyPolicy) int {
		prio := map[ccent.ParameterApplyPolicy]int{
			ccent.ParameterApplyPolicyHotReload:        0,
			ccent.ParameterApplyPolicyRestart:          5,
			ccent.ParameterApplyPolicyForceRestart:     10,
			ccent.ParameterApplyPolicyEnsureUpdateToTC: 15,
			ccent.ParameterApplyPolicyNewInstancesOnly: 20,
		}
		return prio[a] - prio[b]
	})
	return policies, strings.Join(reasons, "\n")
}
