package validation

import (
	"fmt"
	"slices"

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

var (
	CloudConfig = cloudconifgValidator{}
)

type cloudconifgValidator struct{}

func (v cloudconifgValidator) ConfigurationChange(chg *ccent.ConfigurationChange) error {
	if !slices.Contains(ccent.ConfigurationChangeTargetTypeValues, chg.TargetType) {
		return ErrInvalid("target_type", chg.TargetType)
	}
	if chg.TargetVersion == "" && (chg.ChangeType != string(api.ChangeTypeChangefeed) && chg.ChangeType != string(api.ChangeTypeChangefeedNg) && chg.ChangeType != string(api.ChangeTypeChangefeedEssential)) {
		return ErrRequired("target_version")
	}
	if chg.Applicant == "" {
		return ErrRequired("applicant")
	}
	if !slices.Contains(ccent.ParameterApplyPolicyValues, chg.ApplyPolicy) {
		return ErrInvalid("apply_policy", chg.ApplyPolicy)
	}
	if !slices.Contains(ccent.ConfigurationChangePhaseValues, chg.Phase) {
		return ErrInvalid("phase", chg.Phase)
	}

	fileSet := container.NewSet[string]()
	for _, f := range chg.Scope.Files {
		if err := v.ConfigurationFileChange(f); err != nil {
			return err
		}
		if fileSet.Has(f.Filename) {
			return ErrDuplicate("filename", f.Filename)
		}
		fileSet.Insert(f.Filename)
	}

	if chg.Scope.Version != nil {
		if err := v.ConfigurationVersionChange(chg.Scope.Version); err != nil {
			return err
		}
	}

	if chg.ChangedScope().Empty() {
		return fmt.Errorf("nothing to change")
	}

	switch chg.TargetType {
	case ccent.ConfigurationChangeTargetTypeCluster:
		err := v.ClusterConfigurationLocator(chg.TargetCluster)
		if err != nil {
			return err
		}
	case ccent.ConfigurationChangeTargetTypeTenantInit:
		err := v.TenantInitConfigurationLocator(chg.TargetTenantInit)
		if err != nil {
			return err
		}
	}

	return nil
}

func (v cloudconifgValidator) ConfigurationFileChange(chg *ccent.ConfigurationFileChange) error {
	if chg == nil {
		return ErrRequired("nil file change")
	}
	if chg.Filename == "" {
		return ErrRequired("filename")
	}
	if chg.ConfigType == "" {
		return ErrRequired("config_type")
	}
	for _, modif := range chg.ModifiedParameters {
		if modif.ToSource == ccent.ParameterKernelDefault {
			continue
		}
		found := false
		for _, custom := range chg.AllParameters {
			if modif.Name == custom.Name {
				found = true
				break
			}
		}
		if !found {
			return fmt.Errorf("a parameter '%s' is modified but not found in all_parameters", modif.Name)
		}
	}
	return nil
}

func (v cloudconifgValidator) ConfigurationVersionChange(chg *ccent.ConfigurationVersionChange) error {
	if chg.To == "" {
		return ErrRequired("to_version")
	}

	return nil
}

func (v cloudconifgValidator) ClusterConfigurationLocator(loc *ccent.ClusterConfigurationLocator) error {
	if loc == nil {
		return ErrRequired("nil cluster locator")
	}
	if loc.ClusterType == "" {
		return ErrRequired("cluster_type")
	}
	if loc.ClusterID == "" {
		return ErrRequired("cluster_id")
	}
	return nil
}

func (v cloudconifgValidator) TenantInitConfigurationLocator(loc *ccent.TenantInitConfigurationLocator) error {
	if loc == nil {
		return ErrRequired("nil tenant init locator")
	}
	if loc.ClusterType == "" {
		return ErrRequired("cluster_type")
	}
	if loc.TenantID == 0 {
		return ErrRequired("tenant_id")
	}
	return nil
}

func (v cloudconifgValidator) DefaultConfigurationChange(chg *ccent.DefaultConfigurationChange) error {
	if !slices.Contains(ccent.DefaultConfigurationChangeValues, chg.Phase) {
		return ErrInvalid("phase", chg.Phase)
	}
	if chg.Applicant == "" {
		return ErrRequired("applicant")
	}

	return nil
}

func (v cloudconifgValidator) DefaultConfigurationChangeRelease(r *ccent.DefaultConfigurationChangeRelease) error {
	if r == nil {
		return ErrRequired("nil release")
	}
	if r.Environment == "" {
		return ErrRequired("environment")
	}
	return nil
}
