package eks

import (
	"context"
	"fmt"
	"time"

	"github.com/crossplane-contrib/provider-aws/apis/ec2/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpc/task"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
)

type VPC struct {
	*task.BaseTask
}

func NewVPC(ctx *task.Context) *VPC {
	return &VPC{&task.BaseTask{Context: ctx}}
}

// Name implements Task interface.
func (t *VPC) Name() string {
	return "VPC"
}

func (t *VPC) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	// if the resource is migrated, do not create vpc xp cr.
	if t.VPC.Labels[v1alpha1.IsLegacyKey] == "true" {
		return deploy.Done()
	}
	name := t.GetVPCName()
	tags := util.AddResourceName(cloudv1.TagsFromLabels(t.VPC.Labels), name)

	vpc := v1beta1.VPC{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "ec2.aws.crossplane.io/v1beta1",
			Kind:       "VPC",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: t.Namer.Labels(),
		},
		Spec: v1beta1.VPCSpec{
			ForProvider: v1beta1.VPCParameters{
				Region:             pointer.String(t.VPC.Spec.Region),
				CIDRBlock:          t.VPC.Spec.CIDR,
				EnableDNSSupport:   pointer.Bool(true),
				EnableDNSHostNames: pointer.Bool(true),
				Tags:               util.GetEC2V1BetaTags(tags),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: t.ProviderConfigName()},
			},
		},
	}

	if err := k8s.Apply(ctx, t.Local, &vpc); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	return deploy.Done()
}

func (t *VPC) PostCreate(ctx context.Context) deploy.Result {
	name := t.GetVPCName()
	vpc := &v1beta1.VPC{}

	if err := t.Local.Get(ctx, types.NamespacedName{Name: name}, vpc); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	if vpc.Status.AtProvider.VPCState != "available" {
		return deploy.Panic(fmt.Errorf("vpc status not available: %v", vpc.Status.AtProvider.VPCState)).WithRequeuAfter(time.Second * 30)
	}

	if err := t.setVPCStatus(ctx, vpc); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	return deploy.Done()
}

func (t *VPC) Delete(ctx context.Context) deploy.Result {
	name := t.GetVPCName()
	// TODO: check if need to delete vpc endpoints
	vpc := &v1beta1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}

	if err := k8s.DeleteObject(ctx, t.Local, vpc); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *VPC) PostDelete(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: namer.UniqueIdentifier(t.VPC.ObjectMeta)}, &v1beta1.VPC{}))
}

func (t *VPC) setVPCStatus(ctx context.Context, vpcRes *v1beta1.VPC) error {
	vpcID := meta.GetExternalName(vpcRes)
	t.VPC.Status.VPC = v1alpha1.VPCRes{
		VPCID: vpcID,
		Name:  vpcRes.Name,
	}
	// this update won't trigger reconcile, and t.VPC is updated as well.
	return t.VPCRW.UpdateStatus(ctx, t.VPC)
}

func eksClusterNameTagKey(clusterName string) string {
	return fmt.Sprintf("kubernetes.io/cluster/%s", clusterName)
}
