package aks

import (
	"context"
	"fmt"
	"strings"
	"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/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/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"
	"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 {
	t.BeginAt = util.Pointer(time.Now())
	tags := cloudv1.TagsFromLabels(t.VPC.Labels)
	labels := map[string]string{
		v1alpha1.VPCNameKey: t.VPC.Name,
	}
	rg := v1beta1.ResourceGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.resourceGroupName(),
		},
	}

	_, err := controllerutil.CreateOrUpdate(ctx, t.Local, &rg, func() error {
		if t.useExistingResourceGroup() {
			meta.SetExternalName(&rg, t.resourceGroupName())
			rg.Spec = v1beta1.ResourceGroupSpec{
				ResourceSpec: xpv1.ResourceSpec{
					ProviderConfigReference: &xpv1.Reference{
						Name: t.ProviderConfigName(),
					},
					DeletionPolicy:     xpv1.DeletionOrphan,
					ManagementPolicies: existingResourceGroupManagementPolicies(),
				},
			}
			return nil
		}

		rg.Labels = labels
		rg.Spec = v1beta1.ResourceGroupSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
			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 := t.resourceGroupName()
	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)
	}

	if t.useExistingResourceGroup() {
		condition := rg.Status.ConditionedStatus.GetCondition(xpv1.TypeSynced)
		if condition.Status != corev1.ConditionTrue {
			return deploy.Panic(fmt.Errorf("existing resource group %s is not synced", rgName)).WithRequeuAfter(defaultRequeuInterval)
		}
	} else {
		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 {
	rgID := meta.GetExternalName(rg)
	if rgID == "" {
		rgID = rg.Name
	}
	t.VPC.Status.ResourceGroup = v1alpha1.ResourceGroup{
		Name: rg.Name,
		ID:   rgID,
	}
	// 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 {
	if t.useExistingResourceGroup() {
		return deploy.Done()
	}

	rg := &v1beta1.ResourceGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.resourceGroupName(),
		},
	}

	if err := controllers.DeleteUpboundResource(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 {
	if t.useExistingResourceGroup() {
		return deploy.Done()
	}
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: t.resourceGroupName()}, &v1beta1.ResourceGroup{}))
}

func (t *ResourceGroup) resourceGroupName() string {
	return controllers.GetResourceGroupName(t.VPC, t.Config.ResourceNamePrefix)
}

func (t *ResourceGroup) useExistingResourceGroup() bool {
	return useExistingResourceGroup(t.VPC)
}

func useExistingResourceGroup(vpc *v1alpha1.VPC) bool {
	return vpc != nil && vpc.Spec.AccountRef != nil && strings.TrimSpace(vpc.Spec.AccountRef.ResourceGroupName) != ""
}

func existingResourceGroupManagementPolicies() xpv1.ManagementPolicies {
	return xpv1.ManagementPolicies{
		xpv1.ManagementActionObserve,
	}
}
