package gke

import (
	"context"
	"fmt"
	"time"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	gcpvpcpeeringv1beta1 "github.com/crossplane/provider-gcp/apis/vpcpeering/v1beta1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	eksv1alpha1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/eks-provider/controllers/networkpeer/task"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/applier"
	"github.com/tidbcloud/infra-provider/pkg/applier/mutatefuncs"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

const (
	defaultWaitDuration = time.Second * 30
)

type Peer struct {
	*task.BaseTask
}

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

func NewPeer(c *task.Context) *Peer {
	return &Peer{
		BaseTask: &task.BaseTask{
			Context: c,
		},
	}
}

func (t *Peer) Create(ctx context.Context) deploy.Result {
	var peer *gcpvpcpeeringv1beta1.Peering

	switch {
	case t.TargetNetwork != nil && t.TargetGKENetworkSpec != nil:
		peerProject := t.TargetGKENetworkSpec.ProjectID
		peerNetwork := t.TargetGKENetworkSpec.Network

		peer = t.NewVPCPeering(peerProject, peerNetwork)

	case t.NetworkPeer.Spec.Target.GCP != nil:
		target := t.NetworkPeer.Spec.Target.GCP
		peerProject := target.ProjectID
		peerNetwork := target.PeerNetwork

		peer = t.NewVPCPeering(peerProject, peerNetwork)
	default:
		return deploy.Error(fmt.Errorf("neigther network target nor gcp target are set"))
	}

	peer, err := applier.NewApplier(t.Local, mutatefuncs.MutateGCPNetworkPeering).
		Expected(peer).
		Apply(ctx)

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

	if IsReady(peer.Status.Conditions) {
		t.NetworkPeer.Status.Phase = cloudv1.NetworkPeerPhaseActive
	} else {
		t.NetworkPeer.Status.Phase = cloudv1.NetworkPeerPhasePending
	}

	return deploy.Done().WithRequeuAfter(defaultWaitDuration)
}

func (t *Peer) Delete(ctx context.Context) deploy.Result {
	if err := client.IgnoreNotFound(
		t.Local.Delete(ctx, applier.NewClusterObject[*gcpvpcpeeringv1beta1.Peering](vpcPeeringConnectionName(t.NetworkPeer))),
	); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *Peer) PostDelete(ctx context.Context) deploy.Result {
	obj := applier.NewClusterObject[*gcpvpcpeeringv1beta1.Peering](vpcPeeringConnectionName(t.NetworkPeer))
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(obj), obj); err != nil {
		if errors.IsNotFound(err) {
			return deploy.Done()
		}
		return deploy.Error(err)
	}

	return deploy.Error(fmt.Errorf("gcp vpc peering is not deleted"))
}

func (t *Peer) NewVPCPeering(
	peerProject string,
	peerNetwork string,
) *gcpvpcpeeringv1beta1.Peering {
	refName := namer.UniqueIdentifier(t.Network.ObjectMeta)
	return &gcpvpcpeeringv1beta1.Peering{
		ObjectMeta: metav1.ObjectMeta{
			Name: vpcPeeringConnectionName(t.NetworkPeer),
		},
		Spec: gcpvpcpeeringv1beta1.PeeringSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: refName,
				},
			},
			ForProvider: gcpvpcpeeringv1beta1.PeeringParameters{
				Project:            t.GKENetworkSpec.ProjectID,
				Network:            t.GKENetworkSpec.Network,
				Name:               t.NetworkPeer.Name,
				PeerNetwork:        fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/networks/%s", peerProject, peerNetwork),
				ImportCustomRoutes: true,
			},
		},
	}
}

func vpcPeeringConnectionName(peer *cloudv1.NetworkPeer) string {
	if v, ok := peer.Annotations[eksv1alpha1.OwnCrossplaneNameAddKey]; ok {
		return v
	}

	return namer.UniqueIdentifier(peer.ObjectMeta, namer.WithLimit(40))
}

// TODO: move to common utils
func IsReady(conditions []xpv1.Condition) bool {
	if len(conditions) == 0 {
		return false
	}

	for _, c := range conditions {
		if c.Type == xpv1.TypeReady {
			if c.Status == corev1.ConditionTrue {
				return true
			}
		}
	}

	return false
}
