package eks

import (
	"context"
	"errors"

	"github.com/crossplane-contrib/provider-aws/apis/ec2/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"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/internet/task"
)

type InternetGatway struct {
	*task.BaseTask
}

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

func NewInternetGateway(ctx *task.Context) *InternetGatway {
	return &InternetGatway{&task.BaseTask{Context: ctx}}
}

// N.B. the mutate func does nothing for update, so special logic for migrated resource.
// otherwise skip this task for migrated resource
func (i *InternetGatway) Create(ctx context.Context) deploy.Result {
	tags := cloudv1.TagsFromLabels(i.Internet.Labels)

	ig := &v1beta1.InternetGateway{
		ObjectMeta: metav1.ObjectMeta{
			Name: i.internetGatewayName(),
			Labels: map[string]string{
				v1alpha1.InternetNameKey: i.InternetName(),
			},
		},
		Spec: v1beta1.InternetGatewaySpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: i.ProviderConfigName()},
			},
			ForProvider: v1beta1.InternetGatewayParameters{
				Region: pointer.String(i.Internet.Spec.Region),
				VPCIDRef: &xpv1.Reference{
					Name: i.VPC.Status.VPC.Name,
				},
				Tags: util.GetEC2V1BetaTags(tags),
			},
		},
	}
	// TODO(cwen0): why not set the ownerreference?
	// err = controllerutil.SetControllerReference(vpcRes, ig, i.Local.Scheme())
	// if err != nil {
	// 	return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	// }
	_, err := controllerutil.CreateOrUpdate(ctx, i.Local, ig, func() error {
		// TODO: use annotation to migrate existing ig
		// if ig.CreationTimestamp.IsZero() {
		// 	if v, ok := i.VPC.Annotations[eksv1alpha1.NetworkInternetGatewayIDAnnKey]; ok {
		// 		v1alpha1.SetExternalName(ig, v)
		// 	}
		// }
		return nil
	})
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}

	return deploy.Done()
}

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

	ig := &v1beta1.InternetGateway{}
	if err := i.Local.Get(ctx, key, ig); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}

	if len(ig.Status.AtProvider.Attachments) == 0 ||
		ig.Status.AtProvider.Attachments[0].AttachmentStatus != v1beta1.AttachmentStatusAvailable ||
		ig.Status.AtProvider.InternetGatewayID == "" {
		return deploy.Panic(errors.New("internetgateway status not available")).WithRequeuAfter(defaultRequeuInterval)
	}

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

	return deploy.Done()
}

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

func (i *InternetGatway) Delete(ctx context.Context) deploy.Result {
	ig := &v1beta1.InternetGateway{
		ObjectMeta: metav1.ObjectMeta{
			Name: i.internetGatewayName(),
		},
	}

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

	return deploy.Done()
}

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

func (i *InternetGatway) internetGatewayName() string {
	if name := i.Internet.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameInternetGateway]; name != "" {
		return name
	}
	return namer.UniqueIdentifier(i.Internet.ObjectMeta)
}
