package common

import (
	"context"
	"fmt"
	"strings"

	"github.com/tidbcloud/eks-provider/controllers/network/task"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/helm"

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

	corev1 "k8s.io/api/core/v1"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)

const operatorNamespace = "tidb-admin"

type operatorConfig struct {
	operatorImage            string
	tidbBackupManagerImage   string
	advancedStatefulsetImage string

	region string // TiDB-Operator needs this for AWS EBS modifying

	crdPaths      []string
	helmChartPath string
}

func (c *operatorConfig) GetNamespace() string {
	return operatorNamespace
}

func (c *operatorConfig) GetName() string {
	return "tidb-operator"
}

func (c *operatorConfig) ToHelmValues() map[string]interface{} {
	return map[string]interface{}{
		"features": []string{
			"AdvancedStatefulSet=true",
			"VolumeModifying=true",
		},
		"operatorImage":          c.operatorImage,
		"tidbBackupManagerImage": c.tidbBackupManagerImage,
		"advancedStatefulset": map[string]interface{}{
			"create":       true,
			"image":        c.advancedStatefulsetImage,
			"resourceLock": "leases",
		},
		"scheduler": map[string]interface{}{
			// No longer required for Kubernetes 1.19+
			"create": false,
		},
		"controllerManager": map[string]interface{}{
			"leaderResourceLock": "leases",
			"env": []corev1.EnvVar{
				{
					Name:  "AWS_REGION",
					Value: c.region,
				},
			},
			"resources": map[string]interface{}{
				"limits": map[string]interface{}{
					"cpu":    4,
					"memory": "4Gi",
				},
			},
		},
	}
}

func (c operatorConfig) ToHelmChart() (result *chart.Chart, err error) {
	result, err = loader.Load(c.helmChartPath)
	if err != nil {
		return nil, fmt.Errorf("failed to load helm chart %q: %w", c.helmChartPath, err)
	}
	return
}

func (c *operatorConfig) GetCRDs() ([]*apiextensionsv1.CustomResourceDefinition, error) {
	var result []*apiextensionsv1.CustomResourceDefinition
	for _, path := range c.crdPaths {
		crd, err := helm.LoadCRD(path)
		if err != nil {
			return nil, err
		}
		result = append(result, crd...)
	}
	return result, nil
}

type Operator struct {
	task.BaseTask
	config *operatorConfig
}

// Operator installs the TiDB operator from Helm charts. No need to delete
// the operator as it will be deleted along with the EKS cluster itself, i.e.
// no external resource is provisioned.
func NewOperator(ctx *task.Context) *Operator {
	t := &Operator{
		BaseTask: task.BaseTask{Context: ctx},
		config: &operatorConfig{
			operatorImage:            strings.Join([]string{ctx.Config.Network.Operator.Operator.Name, ctx.Config.Network.Operator.Operator.Tag}, ":"),
			tidbBackupManagerImage:   fmt.Sprintf("%s:%s", ctx.Config.Network.Operator.TidbBackupManager.Name, ctx.Config.Network.Operator.TidbBackupManager.Tag),
			advancedStatefulsetImage: ctx.Config.Network.Operator.AdvancedStatefulset.Name,
			region:                   ctx.Network.Spec.Region,
			crdPaths:                 ctx.Config.Network.Operator.CRDPaths,
			helmChartPath:            ctx.Config.Network.Operator.ChartPath,
		},
	}
	return t
}

func (t *Operator) Name() string {
	return "Operator"
}

func (t *Operator) Create(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(helm.Apply(ctx, t.Remote, t.config))
}
