package operate

import (
	"fmt"
	"path"
	"sort"

	v1 "github.com/tidbcloud/infra-component/api/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func addToComponentSet(cs *v1.ComponentSet, c v1.Component) error {
	if cs.Name != c.Name {
		return fmt.Errorf("component %s has a different name than the component set %s", c.Name, cs.Name)
	}
	for _, member := range cs.Spec.Versions {
		if member.Version == c.Spec.Version {
			return nil
		}
	}
	cs.Spec.Versions = append(cs.Spec.Versions, c.Spec)
	return nil
}

// GenerateComponentSets reads the components in the component directory and appends components to the
// existing component set or creates a new component set if the corresponding component set does not
// exist. The modified component sets are then written to the component set directory.
func GenerateComponentSets(cDir string, csDir string) error {
	components, err := readComponentDir(cDir)
	if err != nil {
		return err
	}
	cMap := make(map[string]v1.Component)
	for _, c := range components {
		name := c.Name
		if _, ok := cMap[name]; ok {
			return fmt.Errorf("duplicated component name %s", name)
		}
		if err := ValidateComponent(c); err != nil {
			return err
		}
		cMap[name] = c
	}

	componetSets, err := readComponentSetDir(csDir)
	if err != nil {
		return err
	}
	csMap := make(map[string]v1.ComponentSet)
	for _, cs := range componetSets {
		name := cs.Name
		if _, ok := csMap[name]; ok {
			return fmt.Errorf("duplicated component set name %s", name)
		}
		if err := ValidateComponentSet(cs); err != nil {
			return err
		}
		csMap[name] = cs
	}

	for _, c := range cMap {
		if _, ok := csMap[c.Name]; !ok {
			csMap[c.Name] = v1.ComponentSet{
				ObjectMeta: metav1.ObjectMeta{
					Name: c.Name,
				},
				TypeMeta: metav1.TypeMeta{
					Kind:       "ComponentSet",
					APIVersion: "cloud.pingcap.com/v1",
				},
			}
		}
	}
	for _, cs := range csMap {
		if c, ok := cMap[cs.Name]; ok {
			addToComponentSet(&cs, c)
			sort.Sort(ByVersion(cs.Spec.Versions))
		}
		name := fmt.Sprintf("%s.yaml", cs.Name)
		if err := writeComponentSet(path.Join(csDir, name), cs); err != nil {
			return err
		}
	}
	return nil
}
