package common

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/go-logr/logr"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"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/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

// BuildVPCPeeringLabels creates labels for VPC Peering crossplane CRs.
func BuildVPCPeeringLabels(dp *v1alpha1.DataPlane, vpc *v1alpha1.VPC) map[string]string {
	return map[string]string{
		v1alpha1.DataPlaneIDKey: dp.Name,
		v1alpha1.VPCNameKey:     vpc.Name,
	}
}

// BuildVPCPeeringTags constructs tags for VPC Peering crossplane CRs.
func BuildVPCPeeringTags(vpc *v1alpha1.VPC, name, provider string, extra map[string]string) map[string]*string {
	tags := controllers.ConvertToCloudTags(vpc.Labels, provider)
	tags = util.BuildAWSTagWithName(tags, name)
	tags = util.MergeMaps(tags, extra)
	return util.ToMapPointer(tags)
}

// GenVPCPeeringRouteName generates a unique route name for VPC peering.
// This ensures consistent naming across all cloud providers.
//
// The generated name is truncated to 63 characters to comply with Kubernetes naming constraints.
func GenVPCPeeringRouteName(vpcPeeringName, routeTableName string) string {
	return namer.UniqueIdentifier(metav1.ObjectMeta{
		Name: fmt.Sprintf("%s-%s", vpcPeeringName, routeTableName),
	}, namer.WithLimit(63))
}

// BuildPeeringRouteLabels creates labels for VPC Peering Route CRs.
// These labels are used to query and manage Route CRs associated with a VPC Peering.
//
// The returned labels can be used with client.MatchingLabels to filter routes.
func BuildPeeringRouteLabels(vpcPeeringName string) map[string]string {
	return map[string]string{
		v1alpha1.VPCPeeringNameKey: vpcPeeringName,
	}
}

// GetNodeRouteTableNames extracts RouteTable CR names associated with node subnets.
// It returns Kubernetes CR names (not cloud provider route table IDs).
//
// The filter parameter should match the naming pattern for node route tables:
//   - AWS: "-nodes-"
//   - AliCloud: "-nodes"
//
// Returns an error if the internet CR is nil, has no route tables, or no tables match the filter.
func GetNodeRouteTableNames(internet *v1alpha1.Internet, filter string) ([]string, error) {
	if internet == nil || len(internet.Status.RouteTables) == 0 {
		return nil, fmt.Errorf("internet or internet.status.routeTables is nil/empty")
	}

	routeTableNames := make([]string, 0, 3)
	for _, rt := range internet.Status.RouteTables {
		if strings.Contains(rt.Name, filter) {
			routeTableNames = append(routeTableNames, rt.Name)
		}
	}

	if len(routeTableNames) == 0 {
		return nil, fmt.Errorf("no node route tables found with filter '%s'", filter)
	}

	return routeTableNames, nil
}

// DeleteAllRoutes deletes all routes associated with a VPC peering using the provided RouteManager.
// This function provides a unified deletion flow across all cloud providers.
//
// The deletion process:
//  1. Lists all routes for the VPC peering using the manager
//  2. Iterates through each route and deletes it
//  3. Logs progress for each route
//
// This operation is idempotent - routes that don't exist will be silently ignored.
//
// Parameters:
//   - ctx: Context for the operation
//   - manager: The RouteManager implementation for the specific cloud provider
//   - vpcPeeringName: Name of the VPC peering to delete routes for
//   - logger: Logger for operation tracking
//
// Returns:
//   - An error if listing or deleting routes fails
func DeleteAllRoutes(
	ctx context.Context,
	manager RouteManager,
	vpcPeeringName string,
	logger logr.Logger,
) error {
	routes, err := manager.ListRoutes(ctx, vpcPeeringName)
	if err != nil {
		return fmt.Errorf("failed to list routes for deletion: %w", err)
	}

	if len(routes) == 0 {
		logger.Info("no routes to delete", "vpcPeeringName", vpcPeeringName)
		return nil
	}

	logger.Info("deleting routes for VPC peering",
		"vpcPeeringName", vpcPeeringName,
		"routeCount", len(routes))

	for _, route := range routes {
		logger.Info("deleting route CR for VPC peering",
			"routeName", route.Name,
			"routeTableName", route.RouteTableName)

		if err := manager.DeleteRoute(ctx, route); err != nil {
			return fmt.Errorf("failed to delete route CR %s: %w", route.Name, err)
		}
	}

	logger.Info("successfully deleted all route CRs for VPC peering",
		"vpcPeeringName", vpcPeeringName,
		"routeCount", len(routes))

	return nil
}

// TriggerReconciliation updates annotation to trigger reconciliation loop
func TriggerReconciliation(
	ctx context.Context,
	obj client.Object,
	cli client.Client,
	patch client.Patch,
) error {
	annotations := obj.GetAnnotations()
	if annotations == nil {
		annotations = make(map[string]string)
	}
	annotations[AnnotationKeyManuallyTriggerReconcile] = time.Now().Format(annotationTimeFormat)
	obj.SetAnnotations(annotations)
	return cli.Patch(ctx, obj, patch)
}
