package ack

import (
	"context"
	"errors"
	"time"

	alivpcv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/vpc/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpc/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
)

type vpcTask struct {
	*task.BaseTask
}

func NewVPC(ctx *task.Context) deploy.Task {
	return &vpcTask{&task.BaseTask{Context: ctx}}
}

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

func (t *vpcTask) getVPCName() string {
	return controllers.UniqueIdentifier(t.VPC.ObjectMeta, namer.WithPrefix(t.Config.ResourceNamePrefix))
}

func (t *vpcTask) Create(ctx context.Context) deploy.Result {
	beginAt := time.Now()
	t.BeginAt = &beginAt
	name := t.getVPCName()
	tags := controllers.ConvertToCloudTags(t.VPC.Labels, consts.ProviderTypeACK)
	tags = util.MergeMaps(tags, t.Config.AdditionalCloudTags)

	labels := util.MergeMaps(t.Namer.Labels(), t.VPC.Labels)
	labels[v1alpha1.VPCNameKey] = name

	vpc := &alivpcv1alpha1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
		Spec: alivpcv1alpha1.VPCSpec{
			ForProvider: alivpcv1alpha1.VPCParameters{
				VPCName:   ptr.To(name),
				Region:    ptr.To(t.VPC.Spec.Region),
				CidrBlock: ptr.To(t.VPC.Spec.CIDR),
				Tags:      util.ToMapPointer(tags),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: t.ProviderConfigName()},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
		},
	}

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

	return deploy.Done()
}

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

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

	if !controllers.IsResourceReady(vpc.Status.ResourceStatus) {
		return deploy.Panic(errors.New("vpc is not ready")).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 *vpcTask) Delete(ctx context.Context) deploy.Result {
	name := t.getVPCName()
	vpc := &alivpcv1alpha1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}

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

	return deploy.Done()
}

func (t *vpcTask) PostDelete(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: t.getVPCName()}, &alivpcv1alpha1.VPC{}))
}

func (t *vpcTask) setVPCStatus(ctx context.Context, vpcRes *alivpcv1alpha1.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)
}
