package shadowpool

import (
	"context"
	"fmt"
	"time"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

// Free clusters in shadow pools according to AutoScalingConfig.TimeToFreeInHours
func (w *worker) Free(ctx context.Context) error {
	tilog.Info(ctx, "Starting Free operation for shadow pools")

	// List all enabled shadow pools
	pools, err := w.poolRepo.List(ctx, &repository.ShadowPoolFilter{
		OnlyEnabled: true,
	})
	if err != nil {
		tilog.Error(ctx, "Failed to list shadow pools", zap.Error(err))
		return fmt.Errorf("failed to list shadow pools: %w", err)
	}

	tilog.Info(ctx, "Found shadow pools to free clusters from", zap.Int("count", len(pools)))

	// Process each pool
	for _, pool := range pools {
		if err := w.freeForSingleShadowPool(ctx, pool.ID); err != nil {
			tilog.Error(ctx, "Failed to free clusters from shadow pool",
				zap.Uint64("pool_id", pool.ID),
				zap.String("pool_name", pool.Name),
				zap.Error(err))
			freeFailureTotal.WithLabelValues(
				fmt.Sprintf("%d", pool.ID),
				pool.Name,
			).Inc()
			// Continue with next pool even if one fails
			continue
		}
		freeSuccessTotal.WithLabelValues(
			fmt.Sprintf("%d", pool.ID),
			pool.Name,
		).Inc()
	}

	tilog.Info(ctx, "Completed Free operation for shadow pools")
	return nil
}

func (w *worker) freeForSingleShadowPool(ctx context.Context, poolID uint64) error {
	tilog.Debug(ctx, "Freeing clusters from shadow pool", zap.Uint64("pool_id", poolID))

	// Get the shadow pool
	pool, err := w.poolRepo.Get(ctx, poolID)
	if err != nil {
		return fmt.Errorf("failed to get shadow pool: %w", err)
	}

	ctx = metadata.AppendToOutgoingMeta(ctx, &metadata.RequestMeta{
		UserID:            w.getFakeUserID(),
		OrgID:             pool.OrganizationID,
		DefaultProjectID:  pool.ProjectID,
		ValidProjectList:  []uint64{pool.ProjectID},
		IsCheckPermission: lo.ToPtr(true),
	})

	// Skip if TimeToFreeInHours is not configured
	if pool.AutoScalingConfig.TimeToFreeInHours <= 0 {
		tilog.Debug(ctx, "TimeToFreeInHours not configured, skipping",
			zap.Uint64("pool_id", poolID))
		return nil
	}

	// Query clusters for this pool, handle pagination if needed (for simplicity, we assume there won't be too many clusters in a single pool, so we query a large page size)
	clusters, err := w.clusterRepo.QueryClusterByFilterAndPage(ctx, &repository.ClusterFilter{
		QueryDeleted:  false,
		ShadowPoolIds: []uint64{pool.ID},
		ShadowStates: []entity.ShadowState{
			entity.ShadowStateRented,
			entity.ShadowStateClaimed,
		},
		CreatedAtAsc: true,
	}, 0, 100)
	if err != nil {
		return fmt.Errorf("failed to query clusters: %w", err)
	}

	// Calculate the expiration time
	expirationDuration := time.Duration(pool.AutoScalingConfig.TimeToFreeInHours) * time.Hour
	now := time.Now()

	// Find and delete expired clusters
	freedCount := 0
	for _, cluster := range clusters {
		// Check if cluster has exceeded TimeToFreeInHours
		age := now.Sub(cluster.UpdatedAt)
		if age > expirationDuration {
			tilog.Info(ctx, "Freeing expired shadow pool cluster",
				zap.Uint64("pool_id", poolID),
				zap.Uint64("cluster_id", cluster.ClusterId),
				zap.Duration("age", age),
				zap.Duration("expiration", expirationDuration))

			// Delete the cluster using the DeleteCluster domain service
			_, _, err := w.serverlessClusterSvc.DeleteCluster(ctx, cluster.ClusterId)
			if err != nil {
				tilog.Error(ctx, "Failed to delete expired cluster",
					zap.Uint64("pool_id", poolID),
					zap.Uint64("cluster_id", cluster.ClusterId),
					zap.Error(err))
				// Continue with next cluster even if one fails
				continue
			}

			freedCount++
			tilog.Debug(ctx, "Successfully freed cluster",
				zap.Uint64("pool_id", poolID),
				zap.Uint64("cluster_id", cluster.ClusterId))
		}
	}

	if freedCount > 0 {
		tilog.Info(ctx, "Freed clusters from shadow pool",
			zap.Uint64("pool_id", poolID),
			zap.Int("count", freedCount))
	}

	// Record the number of clusters freed in this operation
	freeQuantity.WithLabelValues(
		fmt.Sprintf("%d", poolID),
		pool.Name,
	).Add(float64(freedCount))

	return nil
}
