package common

import (
	"context"
	"fmt"

	"github.com/aws/aws-sdk-go/aws/arn"
	"github.com/cert-manager/aws-privateca-issuer/pkg/api/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/infra-provider/pkg/cert"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
)

// The issuer is a common task because we use
// AWS PCA as our ca provider for all cloud now.
type Issuer struct {
	*task.BaseTask
}

func NewPCAIssuer(ctx *task.Context) *Issuer {
	return &Issuer{BaseTask: &task.BaseTask{Context: ctx}}
}

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

// The next task cert relies on issuer task success
func (t *Issuer) Create(ctx context.Context) deploy.Result {
	pcaARN := t.PCAGetter.Get()
	if pcaARN == "" {
		return deploy.Panic(fmt.Errorf("component PCA not specified"))
	}

	cli := t.EnvProvider.GetClient()
	iss, mut := t.issuer(pcaARN)
	_, err := controllerutil.CreateOrUpdate(ctx, cli, iss, mut)
	return deploy.PanicOrDone(err)
}

func (t *Issuer) PostCreate(ctx context.Context) deploy.Result {
	cli := t.EnvProvider.GetClient()
	iss := &v1beta1.AWSPCAIssuer{
		ObjectMeta: metav1.ObjectMeta{
			Name:      cert.PCAIssuerName,
			Namespace: t.Namer.GetTiDBNamespace(),
		},
	}

	if err := cli.Get(ctx, client.ObjectKeyFromObject(iss), iss); err != nil {
		return deploy.Panic(err)
	}
	for _, v := range iss.Status.Conditions {
		if v.Type == "Ready" && v.Status == "True" {
			return deploy.Done()
		}
	}
	// When AWSPCAIssuer is not ready, the certificate request would fail to complete
	// and retry takes a long time to complete, so wait here until issuer is ready
	return deploy.Panic(fmt.Errorf("aws pca issuer %v not ready", cert.PCAIssuerName))
}

// TODO: we'd better use AWSPCAClusterIssuer to avoid create AWSPCAIssuer for every cluster
// At least the internal mtls certificate can share the same AWSPCAClusterIssuer
func (t *Issuer) issuer(pcaArn string) (*v1beta1.AWSPCAIssuer, func() error) {
	iss := &v1beta1.AWSPCAIssuer{
		ObjectMeta: metav1.ObjectMeta{
			Name:      cert.PCAIssuerName,
			Namespace: t.Namer.GetTiDBNamespace(),
		},
	}
	mut := func() error {
		resource, err := arn.Parse(pcaArn)
		if err != nil {
			return err
		}
		iss.Spec = v1beta1.AWSPCAIssuerSpec{
			Arn: pcaArn,
			// Thought the spec claims that it could infer the region, it actually not.
			Region: resource.Region,
		}
		return nil
	}
	return iss, mut
}
