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/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/internet/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/util"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
)

const ipv4GatewayInternetMode = "public"

type internetGatwayTask struct {
	*task.BaseTask
}

// Name return the name of the task
func (i *internetGatwayTask) Name() string {
	return "InternetGateway"
}

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

func (i *internetGatwayTask) Create(ctx context.Context) deploy.Result {
	i.BeginAt = util.Pointer(time.Now())
	name := i.internetGatewayName()
	tags := controllers.ConvertToCloudTags(i.Internet.Labels, consts.ProviderTypeACK)
	tags = util.MergeMaps(tags, i.Config.AdditionalCloudTags)
	labels := util.MergeMaps(i.Internet.Labels, map[string]string{v1alpha1.InternetNameKey: i.Internet.Name})

	ig := &alivpcv1alpha1.IPv4Gateway{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
		Spec: alivpcv1alpha1.IPv4GatewaySpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: i.ProviderConfigName()},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
			ForProvider: alivpcv1alpha1.IPv4GatewayParameters{
				IPv4GatewayName: ptr.To(name),
				Region:          ptr.To(i.Internet.Spec.Region),
				VPCIDRef: &xpv1.Reference{
					Name: i.VPCName(),
				},
				Enabled: ptr.To(true),
				// the public network traffic mode of the vpc after the ipv4 gateway is deleted
				InternetMode: ptr.To(ipv4GatewayInternetMode),
				Tags:         util.ToMapPointer(tags),
			},
		},
	}

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

	return deploy.Done()
}

func (i *internetGatwayTask) PostCreate(ctx context.Context) deploy.Result {
	key := types.NamespacedName{
		Name: i.internetGatewayName(),
	}

	ig := &alivpcv1alpha1.IPv4Gateway{}
	if err := i.Local.Get(ctx, key, ig); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	if !controllers.IsResourceReady(ig.Status.ResourceStatus) {
		return deploy.Panic(errors.New("internet gateway status not available")).WithRequeuAfter(time.Second * 30)
	}

	if err := i.setInternetGatewayStatus(ctx, ig); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	return deploy.Done()
}

func (i *internetGatwayTask) setInternetGatewayStatus(ctx context.Context, igRes *alivpcv1alpha1.IPv4Gateway) error {
	i.Internet.Status.InternetGateway = v1alpha1.InternetGateway{
		Name:              igRes.Name,
		InternetGatewayID: *igRes.Status.AtProvider.ID,
	}
	// this update won't trigger reconcile
	return i.InternetRW.UpdateStatus(ctx, i.Internet)
}

func (i *internetGatwayTask) Delete(ctx context.Context) deploy.Result {
	ig := &alivpcv1alpha1.IPv4Gateway{
		ObjectMeta: metav1.ObjectMeta{
			Name: i.internetGatewayName(),
		},
	}
	if err := controllers.DeleteUpboundResource(ctx, i.Local, ig); err != nil {
		return deploy.Error(err).WithRequeuAfter(time.Second * 30)
	}

	return deploy.Done()
}

func (i *internetGatwayTask) PostDelete(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, i.Local, types.NamespacedName{Name: i.internetGatewayName()}, &alivpcv1alpha1.IPv4Gateway{}))
}

func (i *internetGatwayTask) internetGatewayName() string {
	return i.InternetName()
}
