package eks

import (
	"context"
	"fmt"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws/arn"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	awsvpcpeeringv1alpha1 "github.com/crossplane/provider-aws/apis/vpcpeering/v1alpha1"
	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 *awsvpcpeeringv1alpha1.VPCPeeringConnection

	switch {
	case t.TargetNetwork != nil && t.TargetEKSNetworkSpec != nil:
		roleARN, err := arn.Parse(t.TargetEKSNetworkSpec.AssumeRoleArn)
		if err != nil {
			return deploy.Error(err)
		}
		peerRegion := t.TargetNetwork.Spec.Region
		peerCIDR := t.TargetNetwork.Spec.CIDR
		peerVPCID := t.TargetEKSNetworkSpec.VpcId
		peerOwnerID := roleARN.AccountID

		peer = t.NewVPCPeeringConnection(peerRegion, peerCIDR, peerVPCID, peerOwnerID)

	case t.NetworkPeer.Spec.Target.AWS != nil:
		target := t.NetworkPeer.Spec.Target.AWS
		peerRegion := target.PeerRegion
		peerVPCID := target.VpcID
		peerCIDR := target.PeerCIDR
		peerOwnerID := target.AccountID

		peer = t.NewVPCPeeringConnection(peerRegion, peerCIDR, peerVPCID, peerOwnerID)
	default:
		return deploy.Error(fmt.Errorf("neigther network target nor aws target are set"))
	}

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

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

	if peer.Status.AtProvider.VPCPeeringConnectionID != nil {
		t.NetworkPeer.Status.PeeringID = *peer.Status.AtProvider.VPCPeeringConnectionID
	}
	if peer.Status.AtProvider.RequesterVPCInfo != nil && peer.Status.AtProvider.RequesterVPCInfo.CIDRBlock != nil {
		t.NetworkPeer.Status.VpcCidr = *peer.Status.AtProvider.RequesterVPCInfo.CIDRBlock
	}

	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[*awsvpcpeeringv1alpha1.VPCPeeringConnection](vpcPeeringConnectionName(t.NetworkPeer))),
	); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *Peer) PostDelete(ctx context.Context) deploy.Result {
	obj := applier.NewClusterObject[*awsvpcpeeringv1alpha1.VPCPeeringConnection](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("aws vpc peering is not deleted"))
}

// TODO: change to use the upstream package to gen vpc peering
// github.com/crossplane-contrib/provider-aws/apis/ec2/v1alpha1
func (t *Peer) NewVPCPeeringConnection(
	peerRegion string,
	peerCIDR string,
	peerVPCID string,
	peerOwnerID string,
) *awsvpcpeeringv1alpha1.VPCPeeringConnection {
	refName := namer.UniqueIdentifier(t.Network.ObjectMeta)
	return &awsvpcpeeringv1alpha1.VPCPeeringConnection{
		ObjectMeta: metav1.ObjectMeta{
			Name: vpcPeeringConnectionName(t.NetworkPeer),
		},
		Spec: awsvpcpeeringv1alpha1.VPCPeeringConnectionSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: refName,
				},
			},
			ForProvider: awsvpcpeeringv1alpha1.VPCPeeringConnectionParameters{
				Region:      t.Network.Spec.Region,
				PeerRegion:  &peerRegion,
				VPCID:       &t.EKSNetworkSpec.VpcId,
				PeerVPCID:   &peerVPCID,
				PeerCIDR:    &peerCIDR,
				PeerOwnerID: &peerOwnerID,
			},
		},
	}
}

// TODO: use namer.UniqueIdentifier to replace it
// NOTE: keep for compatibility now
func vpcPeeringConnectionName(peer *cloudv1.NetworkPeer) string {
	if v, ok := peer.Annotations[eksv1alpha1.OwnCrossplaneNameAddKey]; ok {
		return v
	}
	return fmt.Sprintf("%s-%s", peer.Namespace, peer.Name)
}

// 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
}
