package common

import (
	"context"
	"fmt"
	"maps"
	"reflect"
	"slices"

	ccproto "github.com/tidbcloud/config-center/proto/config_center"
	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	"github.com/tidbcloud/devops/pkg/infra/clients/configcentercli"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/api"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/cvt"
)

type UnifiedParameterBuilder struct {
	params map[string]*ccent.UnifiedParameter
}

func NewUnifiedParameterBuilder() *UnifiedParameterBuilder {
	s := &UnifiedParameterBuilder{
		params: make(map[string]*ccent.UnifiedParameter),
	}
	return s
}

func (b *UnifiedParameterBuilder) AddCustom(param *ccent.NamedParameter) {
	name := param.Name

	if _, exist := b.params[name]; !exist {
		b.params[name] = &ccent.UnifiedParameter{
			UsedParameter: ccent.UsedParameter{
				NamedParameter: *param,
			},
		}
	}

	b.params[name].Custom = pointer.Pointer(param.ParameterValue)
}

func (b *UnifiedParameterBuilder) AddTemplate(param *ccent.NamedParameter) {
	name := param.Name

	if _, exist := b.params[name]; !exist {
		b.params[name] = &ccent.UnifiedParameter{
			UsedParameter: ccent.UsedParameter{
				NamedParameter: *param,
			},
		}
	}

	b.params[name].Template = pointer.Pointer(param.ParameterValue)
}

func (b *UnifiedParameterBuilder) InjectTemplates(ctx context.Context, svc api.ConfigurationService, clusterType ccent.ClusterType, templateID uint64) error {
	param := api.GetTemplateParam{
		WithDefaultCases: false,
	}
	temlate, err := svc.GetTemplate(ctx, clusterType, templateID, param)
	if err != nil {
		return fmt.Errorf("get configuration template '%d'  failed: %v", templateID, err)
	}
	for _, tmplItem := range temlate.Parameters {
		b.AddTemplate(tmplItem)
	}

	return nil
}

func (b *UnifiedParameterBuilder) AddAttr(attr *ccent.ParameterAttribute) {
	name := attr.Name

	if _, exist := b.params[name]; !exist {
		b.params[name] = &ccent.UnifiedParameter{
			UsedParameter: ccent.UsedParameter{
				NamedParameter: ccent.NamedParameter{Name: name},
			},
		}
	}

	b.params[name].Attribute = attr
}

// InjectAttr query attributes of all paramters and inject them.
func (b *UnifiedParameterBuilder) InjectAttrs(ctx context.Context, clusterType ccproto.ClusterType, configType string, version string, cli *configcentercli.Client) error {
	req := &ccproto.ListConfigurationDictItemsRequest{
		ClusterType:         clusterType,
		ConfigurationType:   &configType,
		Version:             &version,
		ParamKeyIn:          slices.Collect(maps.Keys(b.params)),
		MatchPartialVersion: true,

		Page:     1,
		PageSize: int64(len(b.params) * 2), // double the size to avoid pagination
	}
	resp, err := cli.ConfigAPI.ListConfigurationDictItems(ctx, req)
	if err != nil {
		return fmt.Errorf("failed to list param dict: %w", err)
	}

	dict := make(map[string]*ccproto.ConfigurationDictItem)
	for _, item := range resp.ConfigurationDictItems {
		dict[item.ParamKey] = item
	}

	for key := range b.params {
		if _, exist := dict[key]; !exist {
			continue
		}
		b.AddAttr(cvt.From.ConfigCenter.ParameterAttribute(dict[key]))
	}

	return nil
}

func (b *UnifiedParameterBuilder) Build() map[string]*ccent.UnifiedParameter {
	for _, param := range b.params {
		param.Source = ccent.ParameterKernelDefault
		param.Value = nil
		if param.Attribute != nil {
			param.Value = param.Attribute.DefaultValue
		}

		if param.Template != nil {
			param.Source = ccent.ParameterTemplate
			param.Value = param.Template.Value
		}
		if param.Custom != nil {
			param.Source = ccent.ParameterCustom
			param.Value = param.Custom.Value
		}
	}

	return b.params
}

func DiffParameters(currentParams, modifiedParams map[string]*ccent.UnifiedParameter) []ccent.ChangedParameter {
	// RiskLevel should be set to high by default, in case the dict item is missing.
	result := []ccent.ChangedParameter{}

	for name, cur := range currentParams {
		modif := modifiedParams[name]

		if modif != nil {
			// both exists in current and modified, and value is changed
			if cur.Source != modif.Source || !reflect.DeepEqual(cur.Value, modif.Value) {
				cp := ccent.ChangedParameter{
					Name:       name,
					From:       cur.Value,
					To:         modif.Value,
					FromSource: cur.Source,
					ToSource:   modif.Source,
					RiskLevel:  ccent.RiskLevelHigh,
				}
				if modif.Attribute != nil && modif.Attribute.RiskLevel != "" {
					cp.RiskLevel = modif.Attribute.RiskLevel
				}
				result = append(result, cp)
			}
		} else {
			// exists in current but not found in modified
			// unset in custom config or template config, so back to the engine
			cp := ccent.ChangedParameter{
				Name:       name,
				From:       cur.Value,
				To:         nil,
				FromSource: cur.Source,
				ToSource:   ccent.ParameterKernelDefault,
				RiskLevel:  ccent.RiskLevelHigh,
			}
			if cur.Attribute != nil && cur.Attribute.RiskLevel != "" {
				cp.RiskLevel = cur.Attribute.RiskLevel
			}
			result = append(result, cp)
		}
	}

	for name, modif := range modifiedParams {
		cur := currentParams[name]
		if cur == nil {
			// exists in modified but not found in current
			// add a new param in custom config or template config
			cp := ccent.ChangedParameter{
				Name:       name,
				From:       nil,
				To:         modif.Value,
				FromSource: ccent.ParameterKernelDefault,
				ToSource:   modif.Source,
				RiskLevel:  ccent.RiskLevelHigh,
			}
			if modif.Attribute != nil && modif.Attribute.RiskLevel != "" {
				cp.RiskLevel = modif.Attribute.RiskLevel
			}
			result = append(result, cp)
		}
	}

	return result
}
