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/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/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	"github.com/upbound/provider-azure/apis/network/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 VNet struct {
	*task.BaseTask
}

func NewVNet(ctx *task.Context) *VNet {
	return &VNet{&task.BaseTask{Context: ctx}}
}

var _ deploy.Task = &VNet{}

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

// Create implements Task interface.
// Note: return deploy.Panic once VNet is not set up correctly.
func (t *VNet) 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,
	}
	vnet := v1beta1.VirtualNetwork{
		ObjectMeta: metav1.ObjectMeta{
			Name: getVNetName(t.VPC),
		},
	}

	cidr := t.VPC.Spec.CIDR
	location := t.VPC.Spec.Region
	rgName := controllers.GetResourceGroupName(t.VPC, t.Config.ResourceNamePrefix)

	_, err := controllerutil.CreateOrUpdate(ctx, t.Local, &vnet, func() error {
		vnet.Labels = labels
		vnet.Spec = v1beta1.VirtualNetworkSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
			ForProvider: v1beta1.VirtualNetworkParameters{
				AddressSpace: []*string{
					ptr.To(cidr),
				},
				Location:             ptr.To(location),
				ResourceGroupNameRef: &xpv1.Reference{Name: rgName},
				Tags:                 util.ToMapPointer(tags),
			},
		}

		return nil
	})

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

	return deploy.Done()
}

func getVNetName(vpc *v1alpha1.VPC) string {
	return namer.UniqueIdentifier(vpc.ObjectMeta, namer.WithLimit(controllers.VNetNameLimit))
}

func (t *VNet) PostCreate(ctx context.Context) deploy.Result {
	vnet := &v1beta1.VirtualNetwork{}
	vnetName := getVNetName(t.VPC)
	if err := t.Local.Get(ctx, types.NamespacedName{Name: vnetName}, vnet); err != nil {
		return deploy.Panic(fmt.Errorf("failed to get vnet %s: %w", vnetName, err)).WithRequeuAfter(defaultRequeuInterval)
	}

	condition := vnet.Status.ConditionedStatus.GetCondition(xpv1.TypeReady)
	if condition.Status != corev1.ConditionTrue {
		return deploy.Panic(fmt.Errorf("vpc status not ready: %v", condition.Reason)).WithRequeuAfter(defaultRequeuInterval)
	}

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

	return deploy.Done()
}

func (t *VNet) setVNetStatus(ctx context.Context, vnet *v1beta1.VirtualNetwork) error {
	t.VPC.Status.VPC = v1alpha1.VPCRes{
		Name: vnet.Name,
		// get resource id from status
		VPCID:      meta.GetExternalName(vnet),
		ResourceID: ptr.Deref(vnet.Status.AtProvider.ID, ""),
	}

	// this update won't trigger reconcile
	return t.VPCRW.UpdateStatus(ctx, t.VPC)
}

func (t *VNet) Delete(ctx context.Context) deploy.Result {
	vpc := &v1beta1.VirtualNetwork{
		ObjectMeta: metav1.ObjectMeta{
			Name: getVNetName(t.VPC),
		},
	}
	if err := controllers.DeleteUpboundResource(ctx, t.Local, vpc); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

func (t *VNet) PostDelete(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: getVNetName(t.VPC)}, &v1beta1.VirtualNetwork{}))
}
