package vpc_endpoint

import (
	"context"
	"strconv"
	"strings"

	"github.com/aws/aws-sdk-go-v2/service/ec2/types"
	"github.com/crossplane/provider-aws/apis/ec2/v1alpha1"
	"github.com/pingcap/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/pkg/consts"
	"github.com/tidbcloud/infra-api/pkg/providers/gcp"
)

const TagNameKey = "Name"

// DeleteVpcEndpoint is idempotent.
func DeleteVpcEndpoint(ctx context.Context, cli client.Client, endpointName string) error {
	b := &v1alpha1.VPCEndpoint{
		ObjectMeta: metav1.ObjectMeta{
			Name: endpointName,
		},
	}
	err := cli.Delete(ctx, b)
	if client.IgnoreNotFound(err) != nil {
		return errors.Annotate(err, "failed to delete vpc endpoint")
	}

	return nil
}

func AwsNameTagHasPrefix(tags []types.Tag, prefix string) bool {
	if len(tags) <= 0 {
		return false
	}
	for _, tag := range tags {
		if *tag.Key == TagNameKey && strings.HasPrefix(*tag.Value, prefix) {
			return true
		}
	}
	return false

}

// ShouldUpdateEndpoint compares the current version of the network with the expected version.
// If version in annotations is less than the expected version, it returns true, which means need to be update.
func ShouldUpdateEndpoint(annotations map[string]string, expectedVersion int) bool {
	v, ok := annotations[consts.O11yVersionAnnotationKey]
	if !ok {
		return true
	}
	vNum, err := strconv.Atoi(v)
	if err != nil {
		return true
	}
	return vNum < expectedVersion
}

func AddVersionToAnnotations(annotations map[string]string, currentVersion int) map[string]string {
	if annotations == nil {
		annotations = make(map[string]string)
	}
	annotations[consts.O11yVersionAnnotationKey] = strconv.Itoa(currentVersion)
	return annotations
}

func CleanupEndpoint(ctx context.Context, cli client.Client, k8sIdentifier string, network *cloudv1.Network, gcpApi gcp.GCPAPI, providerSpec *cloudv1.GardenerProviderSpec, shootName string) error {
	// Delete the s3/gsc endpoint if it exists.
	switch network.Spec.Provider {
	case consts.ProviderTypeAWS:
		b := &v1alpha1.VPCEndpoint{
			ObjectMeta: metav1.ObjectMeta{
				Name: buildEndpointName(k8sIdentifier),
			},
		}
		err := cli.Delete(ctx, b)
		if client.IgnoreNotFound(err) != nil {
			return errors.Annotate(err, "failed to delete vpc endpoint")
		}
	case consts.ProviderTypeGCP:
		if err := gcpApi.DeleteGoogleApiPsc(ctx, providerSpec.GCP.ProjectID, shootName); err != nil {
			return errors.Annotate(err, "failed to delete google api psc")
		}
	default:
		return errors.Errorf("unsupported provider type: %s", network.Spec.Provider)
	}
	// Since the lifecycle of S3GatewayEndpoint is not sync to the lifecycle of Network, we need to clean up the annotation on Network when S3GatewayEndpoint is deleted.
	delete(network.Annotations, consts.O11yVersionAnnotationKey)
	if err := cli.Update(ctx, network); err != nil {
		return errors.Annotate(err, "failed to delete network o11y.cloud.pingcap.com/version annotation")
	}

	// Delete the metric endpoint if it exists.
	for k, v := range network.Annotations {
		if strings.HasPrefix(k, consts.O11yClusterEndpointAnnotationKeyPrefix) {
			switch network.Spec.Provider {
			case consts.ProviderTypeAWS:
				// DeleteVpcEndpoint succeeds even if the endpoint is not found.
				if err := DeleteVpcEndpoint(ctx, cli, v); err != nil {
					return errors.Annotate(err, "failed to delete vpc endpoint")
				}
			case consts.ProviderTypeGCP:
				if err := gcpApi.DeletePublishedServicePsc(ctx, providerSpec.GCP.ProjectID, network.Spec.Region, v); err != nil {
					return errors.Annotate(err, "failed to delete published service psc")
				}
			default:
				return errors.Errorf("unsupported provider type: %s", network.Spec.Provider)
			}
			delete(network.Annotations, k)
			if err := cli.Update(ctx, network); err != nil {
				return errors.Annotatef(err, "failed to delete %s annotation", k)
			}
		}
	}
	return nil
}
