package gke

import (
	"context"
	"fmt"
	"time"

	"github.com/crossplane-contrib/provider-gcp/apis/compute/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	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/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpc/task"

	corev1 "k8s.io/api/core/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"
)

type Subnet struct {
	*task.BaseTask
}

func NewSubnet(ctx *task.Context) *Subnet {
	return &Subnet{&task.BaseTask{Context: ctx}}
}

func (t *Subnet) Name() string {
	return "Subnet"
}

func (t *Subnet) Create(ctx context.Context) deploy.Result {
	tags := cloudv1.TagsFromLabels(t.VPC.Labels)
	name := t.GetNetworkName()
	key := types.NamespacedName{
		Name: name,
	}

	vpc := &v1beta1.Network{}
	err := t.Local.Get(ctx, key, vpc)
	if err != nil {
		return deploy.Panic(err)
	}

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

	cidrDispatcher, err := controllers.GetGKECIDRDispatcher(t.VPC, t.Config)
	if err != nil {
		return deploy.Panic(err)
	}

	// 1. create subnetwork.
	subnet := &v1beta1.Subnetwork{
		ObjectMeta: metav1.ObjectMeta{Name: t.GetGKESubnetName()},
		Spec: v1beta1.SubnetworkSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
			},
			ForProvider: v1beta1.SubnetworkParameters{
				IPCidrRange: cidrDispatcher.NodeIPv4CIDRBlock(),
				Region:      t.VPC.Spec.Region,
				// subnetwork doesn't support resource label, we can leave these message in description
				Description: pointer.String(fmt.Sprintf("Tags: %v", tags)),
				NetworkRef: &xpv1.Reference{
					Name: name,
				},
				// enable private ip google access.
				// This field is enabled by default, here just clear to enable it to avioid confusion.
				PrivateIPGoogleAccess: pointer.Bool(true),
			},
		},
	}
	// since the the mutate func does nothing, it's safe for migrated resource.
	if _, err = controllerutil.CreateOrUpdate(ctx, t.Local, subnet, func() error {
		return nil
	}); err != nil {
		return deploy.Panic(err)
	}

	// t.GKENetworkSpec.Subnets = []string{subnet.Name}

	return deploy.Done()
}

func (t *Subnet) PostCreate(ctx context.Context) deploy.Result {
	subnet := &v1beta1.Subnetwork{
		ObjectMeta: metav1.ObjectMeta{Name: t.GetGKESubnetName()},
	}

	if err := t.Local.Get(ctx, types.NamespacedName{Name: subnet.Name}, subnet); err != nil {
		return deploy.Panic(err)
	}

	condition := subnet.Status.ConditionedStatus.GetCondition(xpv1.TypeReady)
	if condition.Status != corev1.ConditionTrue {
		return deploy.Panic(fmt.Errorf("subnet status not ready: %v", condition.Reason)).WithRequeuAfter(time.Second * 30)
	}

	t.VPC.Status.NodeSubnets = []v1alpha1.Subnet{
		{
			SubnetID:  subnet.Name,
			Name:      subnet.Name,
			CIDRBlock: subnet.Spec.ForProvider.IPCidrRange,
		},
	}

	if err := t.VPCRW.UpdateStatus(ctx, t.VPC); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	return deploy.Done()
}

func (t *Subnet) Delete(ctx context.Context) deploy.Result {
	subnetName := t.GetGKESubnetName()

	subnet := &v1beta1.Subnetwork{
		ObjectMeta: metav1.ObjectMeta{
			Name: subnetName,
		},
	}

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

	return deploy.Done()
}

func (t *Subnet) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}
