package aks

import (
	"context"
	"fmt"
	"time"

	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/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpc/task"
	"github.com/upbound/provider-azure/apis/azure/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type ResourceGroup struct {
	*task.BaseTask
}

const defaultRequeuInterval = time.Second * 30

func NewResourceGroup(ctx *task.Context) *ResourceGroup {
	return &ResourceGroup{&task.BaseTask{Context: ctx}}
}

var _ deploy.Task = &ResourceGroup{}

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

// Create implements Task interface.
// Note: return deploy.Panic once ResourceGroup is not set up correctly.
func (t *ResourceGroup) Create(ctx context.Context) deploy.Result {
	tags := cloudv1.TagsFromLabels(t.VPC.Labels)
	t.BeginAt = util.Pointer(time.Now())
	labels := map[string]string{
		v1alpha1.VPCNameKey: t.VPC.Name,
	}
	rg := v1beta1.ResourceGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: controllers.GetResourceGroupName(t.VPC),
		},
	}

	_, err := controllerutil.CreateOrUpdate(ctx, t.Local, &rg, func() error {
		rg.Labels = labels
		rg.Spec = v1beta1.ResourceGroupSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
			},
			ForProvider: v1beta1.ResourceGroupParameters{
				Location: ptr.To(t.VPC.Spec.Region),
				Tags:     util.ToMapPointer(tags),
			},
		}

		return nil
	})
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}

	return deploy.Done()
}

// PostCreate implements Task interface.
func (t *ResourceGroup) PostCreate(ctx context.Context) deploy.Result {
	rg := &v1beta1.ResourceGroup{}
	rgName := controllers.GetResourceGroupName(t.VPC)
	if err := t.Local.Get(ctx, types.NamespacedName{Name: rgName}, rg); err != nil {
		return deploy.Panic(fmt.Errorf("failed to get resource group %s: %w", rgName, err)).WithRequeuAfter(defaultRequeuInterval)
	}

	condition := rg.Status.ConditionedStatus.GetCondition(xpv1.TypeReady)
	if condition.Status != corev1.ConditionTrue {
		return deploy.Panic(fmt.Errorf("resource group %s is not ready", rgName)).WithRequeuAfter(defaultRequeuInterval)
	}

	if err := t.setResourceGroupStatus(ctx, rg); err != nil {
		return deploy.Panic(err)
	}

	return deploy.Done()
}

func (t *ResourceGroup) setResourceGroupStatus(ctx context.Context, rg *v1beta1.ResourceGroup) error {
	t.VPC.Status.ResourceGroup = v1alpha1.ResourceGroup{
		Name: rg.Name,
		ID:   meta.GetExternalName(rg),
	}
	// this update won't trigger reconcile
	return t.VPCRW.UpdateStatus(ctx, t.VPC)
}

// Delete implements Task interface.
func (t *ResourceGroup) Delete(ctx context.Context) deploy.Result {
	rg := &v1beta1.ResourceGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: controllers.GetResourceGroupName(t.VPC),
		},
	}

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

	return deploy.Done()
}

// PostDelete implements Task interface.
func (t *ResourceGroup) PostDelete(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: controllers.GetResourceGroupName(t.VPC)}, &v1beta1.ResourceGroup{}))
}
