package helm

import (
	"context"
	"fmt"
	"strconv"

	hashstructure "github.com/mitchellh/hashstructure/v2"
	"github.com/tidbcloud/eks-provider/pkg/k8s"

	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/chart"

	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
)

const (
	Namespace = "helm"

	valuesIdKey = "helm-values-id"
	chartIdKey  = "helm-chart-id"
)

type Package interface {
	GetName() string
	GetCRDs() ([]*apiextensionsv1.CustomResourceDefinition, error)
	GetNamespace() string
	ToHelmChart() (*chart.Chart, error)
	ToHelmValues() map[string]interface{}
}

func Apply(ctx context.Context, getter k8s.ClientGetter, p Package) error {
	// Apply the CRD first in case the chart needs it
	crds, err := p.GetCRDs()
	if err != nil {
		return fmt.Errorf("failed to get CRDs: %w", err)
	}
	cli, err := getter.GetClient(ctx)
	if err != nil {
		return fmt.Errorf("failed to get Kubernetes client: %w", err)
	}

	for _, crd := range crds {
		if err = cli.Create(ctx, crd); err != nil && !apierrors.IsAlreadyExists(err) {
			return fmt.Errorf("failed to create CRD: %w", err)
		}
	}

	chart, err := p.ToHelmChart()
	if err != nil {
		return fmt.Errorf("failed to load helm chart: %w", err)
	}
	values := p.ToHelmValues()
	if chart.Metadata.Annotations == nil {
		chart.Metadata.Annotations = map[string]string{}
	}

	valuesId, chartId := hash(values), hash(chart)
	chart.Metadata.Annotations[valuesIdKey] = valuesId
	chart.Metadata.Annotations[chartIdKey] = chartId

	config, err := getter.GetHelmConfig(ctx)
	if err != nil {
		return fmt.Errorf("failed to get Helm config: %w", err)
	}
	getclient := action.NewGet(config)

	rel, err := getclient.Run(p.GetName())
	if err != nil {
		// The release does not exist
		installclient := action.NewInstall(config)
		installclient.Namespace = p.GetNamespace()
		installclient.ReleaseName = p.GetName()
		installclient.Wait = false
		installclient.WaitForJobs = false
		_, err = installclient.Run(chart, values)
		return err
	}
	var appliedValuesId, appliedChartId string
	if rel.Chart.Metadata.Annotations != nil {
		appliedValuesId = rel.Chart.Metadata.Annotations[valuesIdKey]
		appliedChartId = rel.Chart.Metadata.Annotations[chartIdKey]
	}
	if appliedValuesId == valuesId && appliedChartId == chartId {
		return nil
	}

	client := action.NewUpgrade(config)
	client.Namespace = p.GetNamespace()
	client.Install = true
	client.Wait = false
	client.WaitForJobs = false
	_, err = client.Run(p.GetName(), chart, values)
	if err != nil {
		return fmt.Errorf("failed to apply helm chart: %w", err)
	}
	return nil
}

func hash(p interface{}) string {
	h, _ := hashstructure.Hash(p, hashstructure.FormatV2, nil)
	return strconv.FormatUint(h, 10)
}
