package region

import (
	"context"
	"encoding/json"
	"fmt"
	"math"
	"slices"
	"sync"
	"time"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/timestamppb"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	regionrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/region"
	resourcepoolrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

const (
	// OwnerTypeOrganization corresponds to ResourcePool.OwnerType.ORGANIZATION = 2
	OwnerTypeOrganization = 2

	// Prometheus query time ranges
	// metricsRateWindow        = "5m" // Rate calculation window
	// metricsHistoryResolution = "1h" // Time resolution for historical data
)

// PrometheusResponse represents the response from Prometheus query API
type PrometheusResponse struct {
	Status string         `json:"status"`
	Data   PrometheusData `json:"data"`
}

// PrometheusData represents the data field in Prometheus response
type PrometheusData struct {
	ResultType string             `json:"resultType"`
	Result     []PrometheusResult `json:"result"`
}

// PrometheusResult represents a single result in Prometheus response
type PrometheusResult struct {
	Metric map[string]string `json:"metric"`
	Values [][]any           `json:"values"` // For range queries: [[timestamp, value], ...]
	Value  []any             `json:"value"`  // For instant queries: [timestamp, value]
}

// ResourcePoolMetrics holds metrics data for a single resource pool
type ResourcePoolMetrics struct {
	ResourcePoolID uint64
	Metrics        []PrometheusResult
	Error          error
}

// queryResourcePoolMetricsAsync queries metrics for a resource pool asynchronously
func (s *service) queryResourcePoolMetricsAsync(
	ctx context.Context,
	resourcePool *entity.ResourcePool,
	startTime int64,
	endTime int64,
	resultChan chan<- ResourcePoolMetrics,
	wg *sync.WaitGroup,
) {
	defer wg.Done()
	defer func() {
		if r := recover(); r != nil {
			tilog.Error(ctx, "panic occurred while querying metrics for resource pool",
				zap.Uint64("resource_pool_id", resourcePool.Id),
				zap.Any("panic", r))
			resultChan <- ResourcePoolMetrics{
				ResourcePoolID: resourcePool.Id,
				Error:          bizerr.New(bizerr.GeneralInternalErrorCode, "panic occurred during metrics query"),
			}
		}
	}()

	start := time.Now()

	expr := `sum(
		kube_node_status_capacity{resource="cpu"}
	* on(node) group_left()
	kube_node_labels{label_tidbcloud_com_component=""}
	)`

	// Query metrics
	metricsData, err := s.metricsClient.QueryResourcePoolMetrics(ctx, resourcePool, expr,
		startTime,
		endTime)
	if err != nil {
		tilog.Warn(ctx, "failed to query metrics for resource pool",
			zap.Uint64("resource_pool_id", resourcePool.Id),
			zap.Error(err),
			zap.Duration("duration", time.Since(start)))
		resultChan <- ResourcePoolMetrics{
			ResourcePoolID: resourcePool.Id,
			Error:          err,
		}
		return
	}

	tilog.Info(ctx, "query metrics for resource pool",
		zap.String("metrics_data", string(metricsData)),
		zap.Uint64("resource_pool_id", resourcePool.Id),
		zap.Duration("duration", time.Since(start)))

	// Parse the Prometheus response
	var promResp PrometheusResponse
	if err := json.Unmarshal(metricsData, &promResp); err != nil {
		tilog.Error(ctx, "failed to unmarshal prometheus response",
			zap.Uint64("resource_pool_id", resourcePool.Id),
			zap.Error(err),
			zap.Duration("duration", time.Since(start)))
		resultChan <- ResourcePoolMetrics{
			ResourcePoolID: resourcePool.Id,
			Error:          bizerr.New(bizerr.GeneralInternalErrorCode, "failed to parse metrics response"),
		}
		return
	}

	if promResp.Status != "success" {
		tilog.Error(ctx, "prometheus query failed",
			zap.Uint64("resource_pool_id", resourcePool.Id),
			zap.String("status", promResp.Status),
			zap.Duration("duration", time.Since(start)))
		resultChan <- ResourcePoolMetrics{
			ResourcePoolID: resourcePool.Id,
			Error:          bizerr.New(bizerr.GeneralInternalErrorCode, "prometheus query failed"),
		}
		return
	}

	resultChan <- ResourcePoolMetrics{
		ResourcePoolID: resourcePool.Id,
		Metrics:        promResp.Data.Result,
		Error:          nil,
	}
}

// aggregateMetricsByTime aggregates metrics from multiple resource pools by timestamp
func aggregateMetricsByTime(metricsResults []ResourcePoolMetrics) map[int64]float64 {
	// Map from timestamp to aggregated value
	aggregated := make(map[int64]float64)

	for _, result := range metricsResults {
		if result.Error != nil {
			continue
		}

		for _, promResult := range result.Metrics {
			// Process each time-value pair in the values array
			for _, valueItem := range promResult.Values {
				if len(valueItem) != 2 {
					continue
				}

				// Extract timestamp and value
				if valueItem[0] == nil || valueItem[1] == nil {
					continue
				}
				timestamp, ok := valueItem[0].(float64)
				if !ok {
					continue
				}

				valueStr, ok := valueItem[1].(string)
				if !ok {
					continue
				}

				var value float64
				if _, err := fmt.Sscanf(valueStr, "%f", &value); err != nil {
					continue
				}

				// Convert timestamp to int64 (Unix timestamp)
				ts := int64(timestamp)
				aggregated[ts] += value
			}
		}
	}

	return aggregated
}

// ListRegionCapacities lists regions with their total vCPU limit capacities
func (s *service) ListRegionCapacities(
	ctx context.Context,
	req *globalngv1.ListRegionCapacitiesRequest,
) (*globalngv1.ListRegionCapacitiesResponse, bizerr.BizError) {
	if req.ServicePlan != globalcommonv1.ServicePlan_BYOC {
		return nil, bizerr.New(bizerr.ErrCodeParams, "only BYOC is supported")
	}

	if req.OrganizationId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "organization_id is required")
	}

	// Build the filter for listing regions
	filter := regionrepo.Filter{
		ServicePlan: req.ServicePlan,
		TenantId:    req.OrganizationId,
		State:       globalcommonv1.Region_ACTIVE.String(),
	}

	if req.CloudProviderId != 0 {
		filter.CloudProvider = req.CloudProviderId
	}

	// List all regions matching the filter
	regions, err := s.regionRepo.ListRegions(ctx, filter)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to query regions")
	}
	tilog.Info(ctx, "found regions", zap.Int("count", len(regions)))

	// Build region capacities
	capacities := make([]*globalngv1.ListRegionCapacitiesResponse_RegionCapacity, 0, len(regions))

	var startTime int64
	var endTimeSec int64
	if req.Usage != nil {
		if req.Usage.EndTime != nil {
			endTimeSec = req.Usage.EndTime.AsTime().Unix()
		} else {
			endTimeSec = time.Now().Unix()
		}

		if req.Usage.StartTime != nil {
			startTime = req.Usage.StartTime.AsTime().Unix()
		} else {
			// Default to 24 hours before endTimeSec if startTime is not provided
			startTime = endTimeSec - 24*3600
		}
	}

	for _, region := range regions {
		// Query resource pools for this region
		rpFilter := &resourcepoolrepo.Filter{
			CloudProviderNames: []entity.CloudProviderName{region.CloudProviderId},
			RegionNames:        []entity.RegionName{region.Id},
			OwnerIds:           []uint64{req.OrganizationId},
			OwnerType:          OwnerTypeOrganization,
			ShowDeleted:        false, // Only include active resource pools
		}
		if req.ServicePlan != globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
			rpFilter.ServicePlans = []entity.ServicePlan{req.ServicePlan}
		}

		resourcePools, _, err := s.resourcePoolRepo.Query(ctx, rpFilter)
		if err != nil {
			return nil, bizerr.WrapError(err, "failed to query resource pools")
		}

		tilog.Info(ctx, "found resource pools for region",
			zap.String("region_id", region.Id.String()),
			zap.Int("count", len(resourcePools)))

		// Calculate total vcpus_limit by summing all resource pool vcpus_limit
		totalVcpusLimit := int64(0)
		for _, rp := range resourcePools {
			totalVcpusLimit += int64(rp.VcpusLimit)
		}

		capacity := &globalngv1.ListRegionCapacitiesResponse_RegionCapacity{
			Region:          region.Region,
			TotalVcpusLimit: totalVcpusLimit,
		}

		// Query historical usage metrics if requested
		if req.Usage != nil {
			// Query metrics for all resource pools in this region concurrently
			var wg sync.WaitGroup
			resultChan := make(chan ResourcePoolMetrics, len(resourcePools))

			queryStart := time.Now()
			for _, rp := range resourcePools {
				wg.Add(1)
				go s.queryResourcePoolMetricsAsync(ctx, rp, startTime, endTimeSec, resultChan, &wg)
			}

			// Wait for all queries to complete
			wg.Wait()
			close(resultChan)
			tilog.Info(ctx, "metrics queries finished",
				zap.String("region_id", region.Id.String()),
				zap.Int("resource_pools_count", len(resourcePools)),
				zap.Duration("duration", time.Since(queryStart)))

			// Collect results
			var metricsResults []ResourcePoolMetrics
			for result := range resultChan {
				metricsResults = append(metricsResults, result)
			}

			// Aggregate metrics by timestamp
			aggregatedMetrics := aggregateMetricsByTime(metricsResults)

			// Convert to UsageMetrics and sort by timestamp
			historyUsages := make([]*globalngv1.ListRegionCapacitiesResponse_UsageMetrics, 0, len(aggregatedMetrics))
			for ts, value := range aggregatedMetrics {
				historyUsages = append(historyUsages, &globalngv1.ListRegionCapacitiesResponse_UsageMetrics{
					MetricsTime: timestamppb.New(time.Unix(ts, 0)),
					UsedVcpus:   int64(math.Round(value)),
				})
			}

			// Sort by timestamp to ensure consistent ordering
			slices.SortFunc(historyUsages, func(a, b *globalngv1.ListRegionCapacitiesResponse_UsageMetrics) int {
				return a.MetricsTime.AsTime().Compare(b.MetricsTime.AsTime())
			})

			if len(historyUsages) > 0 {
				capacity.UsedVcpus = historyUsages[len(historyUsages)-1].UsedVcpus // Set current usage to the latest historical usage
			}
			capacity.HistoryUsages = historyUsages
		}

		capacities = append(capacities, capacity)
	}

	return &globalngv1.ListRegionCapacitiesResponse{
		Capacities: capacities,
	}, nil
}
