package task

import (
	"context"
	"io/ioutil"
	"strings"

	"github.com/tidbcloud/eks-provider/pkg/deploy"
	"github.com/tidbcloud/eks-provider/pkg/helm"

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

	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apimachinery/pkg/util/yaml"
)

const operatorNamespace = "tidb-admin"

type operatorConfig struct {
	operatorImage            string
	tidbBackupManagerImage   string
	advancedStatefulsetImage string
	kubeSchedulerImage       string
	kubeSchedulerImageTag    string

	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",
		},
		"operatorImage":          c.operatorImage,
		"tidbBackupManagerImage": c.tidbBackupManagerImage,
		"advancedStatefulset": map[string]interface{}{
			"create": true,
			"image":  c.advancedStatefulsetImage,
		},
		"scheduler": map[string]interface{}{
			"kubeSchedulerImageName": c.kubeSchedulerImage,
			"kubeSchedulerImageTag":  c.kubeSchedulerImageTag,
		},
		"controllerManager": map[string]interface{}{
			"resources": map[string]interface{}{
				"limits": map[string]interface{}{
					"cpu":    4,
					"memory": "4Gi",
				},
			},
		},
	}
}

func (c operatorConfig) ToHelmChart() (result *chart.Chart, err error) {
	return loader.Load(c.helmChartPath)
}

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

type Operator struct {
	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 *Context) *Operator {
	t := &Operator{
		BaseTask: BaseTask{Context: ctx},
		config: &operatorConfig{
			operatorImage:            strings.Join([]string{ctx.Config.Network.Operator.Operator.Name, ctx.Config.Network.Operator.Operator.Tag}, ":"),
			tidbBackupManagerImage:   ctx.Config.Network.Operator.TidbBackupManager.Name,
			advancedStatefulsetImage: ctx.Config.Network.Operator.AdvancedStatefulset.Name,
			kubeSchedulerImage:       ctx.Config.Network.Operator.KubeScheduler.Name,
			kubeSchedulerImageTag:    ctx.Config.Network.Operator.KubeScheduler.Tag,
			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 {
	if t.remote == nil {
		return deploy.Skip("empty remote client")
	}
	return deploy.FromError(helm.Apply(ctx, t.remote, t.config))
}

func loadCRD(path string) (*apiextensionsv1.CustomResourceDefinition, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var crd apiextensionsv1.CustomResourceDefinition
	if err = yaml.Unmarshal(data, &crd); err != nil {
		return nil, err
	}
	return &crd, nil
}
