package gke

import (
	"context"
	"fmt"
	"time"

	"github.com/crossplane-contrib/provider-gcp/apis/compute/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/pingcap/errors"
	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"

	"github.com/tidbcloud/eks-provider/controllers/network/task"
	taskutils "github.com/tidbcloud/eks-provider/controllers/network/task/utils"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/cloud/gcp"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"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"
)

// VPC manage vpc.
type VPC struct {
	*task.BaseTask
	gcpCli gcp.Client
}

// NewVPC create VPC instance.
func NewVPC(ctx *task.Context, gcpCli gcp.Client) *VPC {
	return &VPC{
		BaseTask: &task.BaseTask{Context: ctx},
		gcpCli:   gcpCli,
	}
}

var _ deploy.Task = &VPC{}

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

// Create implements Task interface.
// Note: return deploy.Panic once VPC is not set up correctly.
func (t *VPC) Create(ctx context.Context) deploy.Result {
	tags := cloudv1.TagsFromLabels(t.Network.Labels)
	t.BeginAt = util.Pointer(time.Now())
	name := namer.UniqueIdentifier(t.Network.ObjectMeta)

	vpc := v1beta1.Network{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}

	_, err := controllerutil.CreateOrUpdate(ctx, t.Local, &vpc, func() error {
		vpc.Labels = t.Namer.Labels()

		vpc.Spec = v1beta1.NetworkSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: name,
				},
			},
			ForProvider: v1beta1.NetworkParameters{
				AutoCreateSubnetworks: pointer.Bool(false),
				Description:           pointer.String(fmt.Sprintf("Tags: %v", tags)),
				RoutingConfig: &v1beta1.NetworkRoutingConfig{
					RoutingMode: "REGIONAL",
				},
			},
		}
		if t.IsMigrate {
			taskutils.SetExternalName(&vpc, t.GKENetworkSpec.Network)
			// Don't delete the actual VPC resource
			// It should be managed by Gardener terraform
			vpc.Spec.ResourceSpec.DeletionPolicy = xpv1.DeletionOrphan
			// Updating this field is not supported by GCP
			vpc.Spec.ForProvider.Description = nil
		}

		return nil
	})

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

	if t.GKENetworkSpec.Network == "" {
		t.GKENetworkSpec.Network = name
	}

	return t.createSubResources(ctx)
}

// PostCreate implements Task interface.
func (t *VPC) PostCreate(ctx context.Context) deploy.Result {
	return deploy.Done()
}

// 1. create subnets.
func (t *VPC) createSubResources(ctx context.Context) deploy.Result {
	tags := cloudv1.TagsFromLabels(t.Network.Labels)

	name := namer.UniqueIdentifier(t.Network.ObjectMeta)
	key := types.NamespacedName{
		Name: name,
	}

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

	for _, v := range vpc.Status.Conditions {
		if v.Type == xpv1.TypeReady && v.Status != corev1.ConditionTrue {
			return deploy.Panic(fmt.Errorf("NAT not ready: %s", v.Message))
		}
	}

	// Set placeholder bit to 1 to migration overlapping network
	if t.IsMigrate && config.FeatureEnabled(t.Config.FeatureGates, config.SetGKECIDRPlaceholder, t.GetTenantID(), t.GetProjectID()) {
		if err := t.GKECIDRDispatcher.SetPrefixPlaceholder(); err != nil {
			return deploy.Panic(err)
		}
	}

	// create nodes subnetwork when network status is not hibernated
	if t.Network.Status.Phase != cloudv1.NetworkPhaseHibernated {
		// 1. create subnetwork.
		subnet := &v1beta1.Subnetwork{
			ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-nodes", name)},
			Spec: v1beta1.SubnetworkSpec{
				ResourceSpec: xpv1.ResourceSpec{
					ProviderConfigReference: &xpv1.Reference{
						Name: name,
					},
				},
				ForProvider: v1beta1.SubnetworkParameters{
					IPCidrRange: t.GKECIDRDispatcher.NodeIPv4CIDRBlock(),
					Region:      t.Network.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,
					},
				},
			},
		}

		if _, err = controllerutil.CreateOrUpdate(ctx, t.Local, subnet, func() error {
			return nil
		}); err != nil {
			return deploy.Panic(err)
		}

		t.GKENetworkSpec.Subnets = []string{subnet.Name}
	}
	// If we doesn't create private GKE, we don't need NAT

	// err = wirteNetworkSpec(ctx, t.Local, t.Network, vpc)
	// if err != nil {
	// 	return deploy.Panic(err)
	// }

	return deploy.Done()
}

// Delete implements Task interface.
func (t *VPC) Delete(ctx context.Context) deploy.Result {
	name := namer.UniqueIdentifier(t.Network.ObjectMeta)
	subnetName := fmt.Sprintf("%s-nodes", name)

	if err := t.gcpCli.DeleteAllPSCEndpoints(ctx, t.GKENetworkSpec.ProjectID, t.Network.Spec.Region, name, subnetName); err != nil {
		return deploy.Error(errors.Annotate(err, "failed to delete all psc endpoints"))
	}

	// delete subnet
	subnet := &v1beta1.Subnetwork{
		ObjectMeta: metav1.ObjectMeta{
			Name: subnetName,
		},
	}
	err := k8s.DeleteObject(ctx, t.Local, subnet)
	if err != nil {
		return deploy.Error(err)
	}

	// delete vpc
	vpc := &v1beta1.Network{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}

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

	return deploy.Done()
}

// PostDelete implements Task interface.
func (t *VPC) PostDelete(ctx context.Context) deploy.Result {
	name := namer.UniqueIdentifier(t.Network.ObjectMeta)
	key := types.NamespacedName{
		Name: name,
	}
	vpc := &v1beta1.Network{}

	err := k8s.GetDeletedObject(ctx, t.Local, key, vpc)
	return deploy.ErrorOrDone(err)
}
