package common

import (
	"context"
	"fmt"
	"time"

	"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"
	"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/util"
)

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

type CertManager struct {
	*task.BaseTask
	config *certManagerConfig
}

type certManagerConfig struct {
	helmChartPath string
	crdPaths      []string

	controllerImage config.ImageConfig
	webhookImage    config.ImageConfig
	cainjectorImage config.ImageConfig
}

func (c *certManagerConfig) GetNamespace() string {
	return certManagerNamespace
}

func (c *certManagerConfig) GetName() string {
	return "cert-manager"
}

func (c *certManagerConfig) 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 *certManagerConfig) ToHelmValues() map[string]interface{} {
	return map[string]interface{}{
		"extraArgs": []string{"--issuer-ambient-credentials"},
		"image": map[string]interface{}{
			"repository": c.controllerImage.Repo,
			"tag":        c.controllerImage.Tag,
		},
		"webhook": map[string]interface{}{
			"image": map[string]interface{}{
				"repository": c.webhookImage.Repo,
				"tag":        c.webhookImage.Tag,
			},
		},
		"cainjector": map[string]interface{}{
			"image": map[string]interface{}{
				"repository": c.cainjectorImage.Repo,
				"tag":        c.cainjectorImage.Tag,
			},
		},
		// Do not wait for the webhook to become ready.
		"startupapicheck": map[string]interface{}{
			"enabled": false,
		},
		// Avoid generate duplicate certificaterequests.
		// See:
		// https://github.com/cert-manager/cert-manager/issues/4956
		// https://github.com/cert-manager/cert-manager/pull/5487
		"featureGates": "StableCertificateRequestName=true",
	}
}

func (c *certManagerConfig) 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 NewCertManager(ctx *task.Context) *CertManager {
	return &CertManager{
		BaseTask: &task.BaseTask{Context: ctx},
		config: &certManagerConfig{
			helmChartPath:   ctx.Config.Network.CertManager.ChartPath,
			crdPaths:        ctx.Config.Network.CertManager.CRDPaths,
			controllerImage: ctx.Config.Network.CertManager.ControllerImage,
			webhookImage:    ctx.Config.Network.CertManager.WebhookImage,
			cainjectorImage: ctx.Config.Network.CertManager.CAInjectorImage,
		},
	}
}

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

// The next task: PCAIssuer relies on cert-manager to succeed
func (t *CertManager) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	return deploy.PanicOrDone(helm.Apply(ctx, t.Remote, t.config))
}

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

	err = ensureCertManagerReady(ctx, cli)
	return deploy.PanicOrDone(err)
}

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")
}
