package metrics

import (
	"context"
	"fmt"
	"math"
	"sort"

	"github.com/montanaflynn/stats"
	dtoMetrics "github.com/pingcap-inc/clinic/clinic_data_service/pkg/dto/metrics"
	log2 "github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	"github.com/prometheus/common/model"
	"go.uber.org/zap"
)

func getResourceDiffResult(ctx context.Context, rawResult []dtoMetrics.QueryBatchResult) []dtoMetrics.QueryPoints {
	var results []dtoMetrics.QueryPoints
	for _, result := range rawResult {
		primaryValues := extractValuesFromModelValue(ctx, result.PrimaryQueryResult)
		baselineValues := extractValuesFromModelValue(ctx, result.BaseLineQueryResult)

		if len(primaryValues) == 0 || len(baselineValues) == 0 {
			log2.Warn(ctx, "Skipping query due to empty values",
				zap.String("queryName", result.Name),
				zap.Int("primaryCount", len(primaryValues)),
				zap.Int("baselineCount", len(baselineValues)))
			continue
		}

		trimmedPrimary := removeTopAndBottom5Percent(primaryValues)
		trimmedBaseline := removeTopAndBottom5Percent(baselineValues)

		if len(trimmedPrimary) == 0 || len(trimmedBaseline) == 0 {
			continue
		}

		primaryP90, err := stats.Percentile(trimmedPrimary, 90)
		if err != nil {
			log2.Error(ctx, "Failed to calculate primary P90", zap.String("queryName", result.Name), zap.Error(err))
			continue
		}
		primaryAvg, err := stats.Mean(trimmedPrimary)
		if err != nil {
			log2.Error(ctx, "Failed to calculate primary Avg", zap.String("queryName", result.Name), zap.Error(err))
			continue
		}
		baselineP90, err := stats.Percentile(trimmedBaseline, 90)
		if err != nil {
			log2.Error(ctx, "Failed to calculate baseline P90", zap.String("queryName", result.Name), zap.Error(err))
			continue
		}
		baselineAvg, err := stats.Mean(trimmedBaseline)
		if err != nil {
			log2.Error(ctx, "Failed to calculate baseline Avg", zap.String("queryName", result.Name), zap.Error(err))
			continue
		}
		// Calculate Max and Mean from original primaryValues for DiffResult fields
		primaryMax, err := stats.Max(primaryValues)
		if err != nil {
			log2.Error(ctx, "Failed to calculate primary Max", zap.String("queryName", result.Name), zap.Error(err))
			continue
		}
		primaryMean, err := stats.Mean(primaryValues)
		if err != nil {
			log2.Error(ctx, "Failed to calculate primary Mean", zap.String("queryName", result.Name), zap.Error(err))
			continue
		}


		var diffResults []dtoMetrics.DiffResult
		if baselineP90 != 0 {
			diffP90 := (primaryP90 / baselineP90) - 1
			diffResults = append(diffResults, dtoMetrics.DiffResult{
				TimeAggregation: "P90",
				Max:             primaryMax,
				Avg:             primaryMean,
				Diff:            diffP90,
			})
		} else {
			log2.Warn(ctx, "Baseline P90 is zero, cannot calculate ratio", zap.String("queryName", result.Name))
		}

		if baselineAvg != 0 {
			diffAvg := (primaryAvg / baselineAvg) - 1
			diffResults = append(diffResults, dtoMetrics.DiffResult{
				TimeAggregation: "AVG",
				Max:             primaryMax,
				Avg:             primaryMean,
				Diff:            diffAvg,
			})
		} else {
			log2.Warn(ctx, "Baseline Avg is zero, cannot calculate ratio", zap.String("queryName", result.Name))
		}

		// Only add when there is at least one result
		if len(diffResults) > 0 {
			results = append(results, dtoMetrics.QueryPoints{
				TypeName:   result.Name,
				DiffResult: diffResults,
				PromSQL:    result.PrimaryQuery,
			})
		}
	}

	return results
}

func extractValuesFromModelValue(ctx context.Context, value model.Value) []float64 {
	if value == nil {
		log2.Warn(ctx, "extractValuesFromModelValue: value is nil")
		return nil
	}

	var values []float64

	switch v := value.(type) {
	case model.Matrix:
		if len(v) == 0 {
			log2.Warn(ctx, "extractValuesFromModelValue: Matrix is empty")
			return nil
		}
		for _, stream := range v {
			if stream.Values != nil && len(stream.Values) > 0 {
				for _, pair := range stream.Values {
					values = append(values, float64(pair.Value))
				}
			}
		}
		if len(values) == 0 {
			log2.Warn(ctx, "extractValuesFromModelValue: Matrix has no valid values")
		}
	case model.Vector:
		if len(v) == 0 {
			log2.Warn(ctx, "extractValuesFromModelValue: Vector is empty")
			return nil
		}
		for _, sample := range v {
			values = append(values, float64(sample.Value))
		}
	case *model.Scalar:
		values = append(values, float64(v.Value))
	default:
		log2.Warn(ctx, "extractValuesFromModelValue: unsupported type", zap.String("type", fmt.Sprintf("%T", value)))
		return nil
	}

	return values
}

func removeTopAndBottom5Percent(values []float64) []float64 {
	if len(values) == 0 {
		return values
	}

	sortedValues := make([]float64, len(values))
	copy(sortedValues, values)
	sort.Float64s(sortedValues)

	removeCount := int(math.Ceil(float64(len(sortedValues)) * 0.05))
	if removeCount == 0 {
		removeCount = 1
	}

	if removeCount*2 >= len(sortedValues) {
		midIndex := len(sortedValues) / 2
		return []float64{sortedValues[midIndex]}
	}

	startIndex := removeCount
	endIndex := len(sortedValues) - removeCount

	return sortedValues[startIndex:endIndex]
}

func getPerformanceDiffResult(ctx context.Context, rawResult []dtoMetrics.QueryBatchResult) []dtoMetrics.QueryPoints {
	var results []dtoMetrics.QueryPoints
	for _, result := range rawResult {
		primaryStreams := convertToStreams(result.PrimaryQueryResult)
		baselineStreams := convertToStreams(result.BaseLineQueryResult)

		baselineMap := make(map[string]*model.SampleStream)
		for _, stream := range baselineStreams {
			key := stream.Metric.String()
			if _, exists := baselineMap[key]; !exists {
				baselineMap[key] = stream
			}
		}

		for _, primaryStream := range primaryStreams {
			if len(primaryStream.Values) == 0 {
				continue
			}

			primaryValues := extractValuesFromSampleStream(primaryStream)

			baselineKey := primaryStream.Metric.String()
			baselineStream, exists := baselineMap[baselineKey]
			if !exists {
				log2.Warn(ctx, "No matching baseline stream found",
					zap.String("queryName", result.Name),
					zap.String("metric", baselineKey))
				continue
			}

			baselineValues := extractValuesFromSampleStream(baselineStream)
			if len(baselineValues) == 0 {
				log2.Warn(ctx, "Baseline stream has no values",
					zap.String("queryName", result.Name),
					zap.String("metric", baselineKey))
				continue
			}

			streamResult := calculateStreamResult(ctx, primaryValues, baselineValues, result.Name, primaryStream.Metric, result.PrimaryQuery)
			if streamResult != nil {
				results = append(results, *streamResult)
			}
		}
	}

	return results
}

func convertToStreams(value model.Value) []*model.SampleStream {
	if value == nil {
		return nil
	}

	switch v := value.(type) {
	case model.Matrix:
		streams := make([]*model.SampleStream, len(v))
		copy(streams, v)
		return streams
	case *model.Scalar:
		return []*model.SampleStream{
			{
				Metric: nil,
				Values: []model.SamplePair{
					{Timestamp: v.Timestamp, Value: v.Value},
				},
			},
		}
	default:
		return nil
	}
}

func extractValuesFromSampleStream(stream *model.SampleStream) []float64 {
	if stream == nil || len(stream.Values) == 0 {
		return nil
	}

	values := make([]float64, 0, len(stream.Values))
	for _, pair := range stream.Values {
		values = append(values, float64(pair.Value))
	}
	return values
}

func calculateStreamResult(ctx context.Context, primaryValues, baselineValues []float64, queryName string, metric model.Metric, promSQL string) *dtoMetrics.QueryPoints {
	primaryMax, err := stats.Max(primaryValues)
	if err != nil {
		log2.Error(ctx, "Failed to calculate primary Max",
			zap.String("queryName", queryName),
			zap.Error(err))
		return nil
	}
	primaryAvg, err := stats.Mean(primaryValues)
	if err != nil {
		log2.Error(ctx, "Failed to calculate primary Avg",
			zap.String("queryName", queryName),
			zap.Error(err))
		return nil
	}

	trimmedPrimary := removeTopAndBottom5Percent(primaryValues)
	trimmedBaseline := removeTopAndBottom5Percent(baselineValues)

	if len(trimmedPrimary) == 0 || len(trimmedBaseline) == 0 {
		return nil
	}

	trimmedPrimaryAvg, err := stats.Mean(trimmedPrimary)
	if err != nil {
		log2.Error(ctx, "Failed to calculate trimmed primary Avg",
			zap.String("queryName", queryName),
			zap.Error(err))
		return nil
	}
	trimmedBaselineAvg, err := stats.Mean(trimmedBaseline)
	if err != nil {
		log2.Error(ctx, "Failed to calculate trimmed baseline Avg",
			zap.String("queryName", queryName),
			zap.Error(err))
		return nil
	}

	var diffResults []dtoMetrics.DiffResult

	if trimmedBaselineAvg != 0 {
		diffAvg := (trimmedPrimaryAvg / trimmedBaselineAvg) - 1
		diffResults = append(diffResults, dtoMetrics.DiffResult{
			Diff: diffAvg,
			Max:  primaryMax,
			Avg:  primaryAvg,
		})
	} else {
		log2.Warn(ctx, "Trimmed baseline Avg is zero, cannot calculate ratio",
			zap.String("queryName", queryName))
		return nil
	}

	typeName := queryName
	if len(metric) > 0 {
		for _, labelValue := range metric {
			if string(labelValue) != "" {
				typeName = fmt.Sprintf("%s_%s", queryName, labelValue)
				break
			}
		}
	}

	return &dtoMetrics.QueryPoints{
		TypeName:   typeName,
		DiffResult: diffResults,
		PromSQL:    promSQL,
	}
}
