package eks

import (
	"context"
	"fmt"

	"helm.sh/helm/v3/pkg/chart"
	"helm.sh/helm/v3/pkg/chart/loader"
	appsv1 "k8s.io/api/apps/v1"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

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

const (
	certManagerWebhookName = "cert-manager-webhook"
)

type AWSLoadBalancerController struct {
	*task.BaseTask
	config *awsLoadBalancerControllerConfig
}

type awsLoadBalancerControllerConfig struct {
	helmChartPath   string
	crdPaths        []string
	clusterName     string
	controllerImage config.ImageConfig
}

func (c *awsLoadBalancerControllerConfig) GetNamespace() string {
	return metav1.NamespaceSystem
}

func (c *awsLoadBalancerControllerConfig) GetName() string {
	return "aws-load-balancer-controller"
}

func (c *awsLoadBalancerControllerConfig) 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 *awsLoadBalancerControllerConfig) ToHelmValues() map[string]interface{} {
	return map[string]interface{}{
		"clusterName":                  c.clusterName,
		"enableCertManager":            true,
		"enablePodReadinessGateInject": true,
		// https://github.com/kubernetes-sigs/aws-load-balancer-controller/issues/1974
		"targetgroupbindingMaxExponentialBackoffDelay": "60s",
		"image": map[string]interface{}{
			"repository": c.controllerImage.Repo,
			"tag":        c.controllerImage.Tag,
		},
	}
}

func (c *awsLoadBalancerControllerConfig) 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
}

func NewAWSLoadBalancerController(ctx *task.Context) *AWSLoadBalancerController {
	t := &task.BaseTask{Context: ctx}

	return &AWSLoadBalancerController{
		BaseTask: t,
		config: &awsLoadBalancerControllerConfig{
			helmChartPath:   ctx.Config.Network.AWSLoadBalancerController.ChartPath,
			crdPaths:        ctx.Config.Network.AWSLoadBalancerController.CRDPaths,
			clusterName:     namer.LegacyBuildClusterNameByNetwork(t.Network),
			controllerImage: ctx.Config.Network.AWSLoadBalancerController.ControllerImage,
		},
	}
}

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

func (t *AWSLoadBalancerController) Create(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	err = ensureCertManagerReady(ctx, cli)
	if err != nil {
		return deploy.Error(err)
	}

	return deploy.ErrorOrDone(helm.Apply(ctx, t.Remote, t.config))
}

func ensureCertManagerReady(ctx context.Context, cli client.Client) error {
	app := new(appsv1.Deployment)
	if err := cli.Get(ctx, types.NamespacedName{
		Namespace: task.CertManagerNamespace,
		Name:      certManagerWebhookName,
	}, app); err != nil {
		return err
	}

	if utils.IsStatusConditionTrue(app.Status.Conditions, appsv1.DeploymentAvailable) {
		return nil
	}
	return fmt.Errorf("cert-manager webhook not ready")
}
