package system

import (
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/metrics"
)

// LLMStatsSummaryRow is a per-API-key row in the /stats/llm/summary response.
// Exported so aggregate (bundle) endpoints can reuse the payload shape.
type LLMStatsSummaryRow struct {
	KeyID             string  `json:"keyId"`
	MaskedKey         string  `json:"maskedKey"`
	Count6h           uint64  `json:"count6h"`
	Count12h          uint64  `json:"count12h"`
	Count1d           uint64  `json:"count1d"`
	Count3d           uint64  `json:"count3d"`
	Count1w           uint64  `json:"count1w"`
	InputTokens       uint64  `json:"inputTokens"`
	CachedInputTokens uint64  `json:"cachedInputTokens"`
	OutputTokens      uint64  `json:"outputTokens"`
	TokenSpeed        float64 `json:"tokenSpeed"`
}

// BuildLLMStatsSummary collects per-API-key stats from the default collector.
// Returns an empty slice when the collector is not configured.
func BuildLLMStatsSummary() []LLMStatsSummaryRow {
	collector := metrics.DefaultStatsQuerier()
	if collector == nil {
		return []LLMStatsSummaryRow{}
	}

	allStats := collector.StatsSummary()
	rows := make([]LLMStatsSummaryRow, 0, len(allStats))

	for _, aks := range allStats {
		modelsStats := aks.Stats()

		var key6h, key12h, key1d, key3d, key1w uint64
		var keyInputToken, keyCachedInputToken, keyOutputToken, keyDuration uint64

		for _, stat := range modelsStats {
			key6h += stat[metrics.TimeWindow6Hours].Count
			key12h += stat[metrics.TimeWindow12Hours].Count
			key1d += stat[metrics.TimeWindow1Day].Count
			key3d += stat[metrics.TimeWindow3Days].Count
			key1w += stat[metrics.TimeWindow1Week].Count

			keyInputToken += stat[metrics.TimeWindow1Week].Input
			keyCachedInputToken += stat[metrics.TimeWindow1Week].CachedInput
			keyOutputToken += stat[metrics.TimeWindow1Week].Output
			keyDuration += stat[metrics.TimeWindow1Week].Duration
		}

		var tokenSpeed float64
		if keyDuration > 0 {
			durationSeconds := float64(keyDuration) / 1000.0
			tokenSpeed = float64(keyOutputToken) / durationSeconds
		}

		rows = append(rows, LLMStatsSummaryRow{
			KeyID:             aks.KeyID,
			MaskedKey:         aks.MaskedKey,
			Count6h:           key6h,
			Count12h:          key12h,
			Count1d:           key1d,
			Count3d:           key3d,
			Count1w:           key1w,
			InputTokens:       keyInputToken,
			CachedInputTokens: keyCachedInputToken,
			OutputTokens:      keyOutputToken,
			TokenSpeed:        tokenSpeed,
		})
	}

	sort.Slice(rows, func(i, j int) bool {
		if rows[i].Count6h != rows[j].Count6h {
			return rows[i].Count6h > rows[j].Count6h
		}
		if rows[i].Count12h != rows[j].Count12h {
			return rows[i].Count12h > rows[j].Count12h
		}
		return rows[i].Count1d > rows[j].Count1d
	})

	return rows
}

// LLMTimeSeriesDataPoint is exported for reuse by aggregate endpoints.
type LLMTimeSeriesDataPoint struct {
	Timestamp int64  `json:"timestamp"`
	Count     uint64 `json:"count"`
	Input     uint64 `json:"input"`
	Cached    uint64 `json:"cached"`
	Output    uint64 `json:"output"`
}

// LLMTimeSeriesResponse mirrors the /stats/llm/timeseries JSON shape.
type LLMTimeSeriesResponse struct {
	Interval int                      `json:"interval"`
	Data     []LLMTimeSeriesDataPoint `json:"data"`
}

// BuildLLMTimeSeries returns the aggregate LLM time-series data for the
// given window. Returns an empty response when the collector is missing.
func BuildLLMTimeSeries(window metrics.TimeWindow) LLMTimeSeriesResponse {
	collector := metrics.DefaultTimeSeriesQuerier()
	if collector == nil {
		return LLMTimeSeriesResponse{Interval: timeSeriesInterval(window), Data: []LLMTimeSeriesDataPoint{}}
	}
	dps := collector.QueryTimeSeries(window)
	interval := timeSeriesInterval(window)
	data := make([]LLMTimeSeriesDataPoint, len(dps))
	for i, dp := range dps {
		data[i] = LLMTimeSeriesDataPoint{
			Timestamp: dp.Timestamp,
			Count:     dp.Count,
			Input:     dp.Input,
			Cached:    dp.Cached,
			Output:    dp.Output,
		}
	}
	return LLMTimeSeriesResponse{Interval: interval, Data: data}
}

func timeSeriesInterval(window metrics.TimeWindow) int {
	return int(metrics.TimeSeriesAggregationInterval(window).Seconds())
}

// LLMModelTimeSeriesEntry holds time-series data for a single model.
type LLMModelTimeSeriesEntry struct {
	Model     string                   `json:"model"`
	ModelName string                   `json:"modelName"`
	Data      []LLMTimeSeriesDataPoint `json:"data"`
}

// BuildLLMModelTimeSeries returns per-model time-series data for the given
// window. The model list is derived from the current StatsSummary.
func BuildLLMModelTimeSeries(window metrics.TimeWindow) []LLMModelTimeSeriesEntry {
	collector := metrics.DefaultCollector()
	if collector == nil {
		return []LLMModelTimeSeriesEntry{}
	}

	// Collect all distinct models from all API keys.
	allStats := collector.StatsSummary()
	modelSet := make(map[string]struct{})
	for _, aks := range allStats {
		for fullModel := range aks.Stats() {
			modelSet[fullModel] = struct{}{}
		}
	}

	models := make([]string, 0, len(modelSet))
	for m := range modelSet {
		models = append(models, m)
	}
	sort.Strings(models)

	entries := make([]LLMModelTimeSeriesEntry, 0, len(models))
	for _, fullModel := range models {
		dps := collector.QueryTimeSeriesByModel(window, fullModel)
		data := make([]LLMTimeSeriesDataPoint, len(dps))
		for i, dp := range dps {
			data[i] = LLMTimeSeriesDataPoint{
				Timestamp: dp.Timestamp,
				Count:     dp.Count,
				Input:     dp.Input,
				Cached:    dp.Cached,
				Output:    dp.Output,
			}
		}
		entries = append(entries, LLMModelTimeSeriesEntry{
			Model:     fullModel,
			ModelName: extractModelName(fullModel),
			Data:      data,
		})
	}

	return entries
}

// LLMTokenMetric identifies the token field used by model bar-series builders.
type LLMTokenMetric string

const (
	LLMTokenMetricInput  LLMTokenMetric = "input"
	LLMTokenMetricCached LLMTokenMetric = "cached"
	LLMTokenMetricOutput LLMTokenMetric = "output"
)

// BuildLLMModelTokenBarSeries returns coarse token buckets grouped by model for
// overview bar charts.
func BuildLLMModelTokenBarSeries(window metrics.TimeWindow, tokenMetric LLMTokenMetric) metrics.BarSeriesResponse {
	interval := metrics.BarAggregationInterval(window)
	empty := metrics.BarSeriesResponse{
		Interval: int(interval.Seconds()),
		Series:   []metrics.BarSeriesEntry{},
		Data:     []metrics.BarSeriesPoint{},
	}

	collector := metrics.DefaultCollector()
	if collector == nil {
		return empty
	}

	allStats := collector.StatsSummary()
	modelSet := make(map[string]struct{})
	for _, aks := range allStats {
		for fullModel := range aks.Stats() {
			modelSet[fullModel] = struct{}{}
		}
	}

	models := make([]string, 0, len(modelSet))
	for model := range modelSet {
		models = append(models, model)
	}
	sort.Strings(models)
	if len(models) == 0 {
		return empty
	}

	now := time.Now()
	cutoff := now.Add(-window.Duration())
	firstBucket := cutoff.Truncate(interval)
	lastBucket := now.Truncate(interval)

	bucketValues := make(map[int64]map[string]uint64)
	for _, fullModel := range models {
		for _, point := range collector.QueryTimeSeriesByModel(window, fullModel) {
			pointTime := time.Unix(point.Timestamp, 0)
			if pointTime.Before(cutoff) || pointTime.After(now) {
				continue
			}
			bucketTS := pointTime.Truncate(interval).Unix()
			values := bucketValues[bucketTS]
			if values == nil {
				values = make(map[string]uint64)
				bucketValues[bucketTS] = values
			}
			values[fullModel] += tokenMetricValue(point, tokenMetric)
		}
	}

	series := make([]metrics.BarSeriesEntry, 0, len(models))
	for _, fullModel := range models {
		series = append(series, metrics.BarSeriesEntry{
			Key:   fullModel,
			Label: extractModelName(fullModel),
		})
	}

	data := make([]metrics.BarSeriesPoint, 0, int(lastBucket.Sub(firstBucket)/interval)+1)
	for t := firstBucket; !t.After(lastBucket); t = t.Add(interval) {
		values := make(map[string]uint64, len(models))
		existing := bucketValues[t.Unix()]
		for _, fullModel := range models {
			values[fullModel] = existing[fullModel]
		}
		data = append(data, metrics.BarSeriesPoint{
			Timestamp: t.Unix(),
			Values:    values,
		})
	}

	return metrics.BarSeriesResponse{
		Interval: int(interval.Seconds()),
		Series:   series,
		Data:     data,
	}
}

func tokenMetricValue(point metrics.TimeSeriesDataPoint, tokenMetric LLMTokenMetric) uint64 {
	switch tokenMetric {
	case LLMTokenMetricCached:
		return point.Cached
	case LLMTokenMetricOutput:
		return point.Output
	default:
		return point.Input
	}
}

// NewLLMStatsSummaryHandler creates a handler that returns summary statistics aggregated by API key.
func NewLLMStatsSummaryHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"stats": BuildLLMStatsSummary()})
	}
}

// extractModelName extracts the base model name from "provider/model" format.
func extractModelName(fullModelName string) string {
	parts := strings.SplitN(fullModelName, "/", 2)
	if len(parts) == 2 {
		return parts[1]
	}
	return fullModelName
}

// extractProviderName extracts the provider name from "provider/model" format.
func extractProviderName(fullModelName string) string {
	parts := strings.SplitN(fullModelName, "/", 2)
	if len(parts) == 2 {
		return parts[0]
	}
	return "unknown"
}

// NewLLMStatsDetailHandler creates a handler that returns detailed statistics with per-model breakdowns.
// Query parameter "keyId" can be used to filter results for a specific API key.
func NewLLMStatsDetailHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		collector := metrics.DefaultStatsQuerier()
		if collector == nil {
			c.JSON(http.StatusOK, gin.H{"stats": []struct{}{}})
			return
		}

		allStats := collector.StatsSummary()

		filterKeyID := c.Query("keyId")

		type ModelStat struct {
			Model             string  `json:"model"`
			ModelName         string  `json:"modelName"`
			Provider          string  `json:"provider"`
			Count6h           uint64  `json:"count6h"`
			Count12h          uint64  `json:"count12h"`
			Count1d           uint64  `json:"count1d"`
			Count3d           uint64  `json:"count3d"`
			Count1w           uint64  `json:"count1w"`
			InputTokens       uint64  `json:"inputTokens"`
			CachedInputTokens uint64  `json:"cachedInputTokens"`
			OutputTokens      uint64  `json:"outputTokens"`
			TokenSpeed        float64 `json:"tokenSpeed"`
		}

		type APIKeyDetail struct {
			KeyID     string      `json:"keyId"`
			MaskedKey string      `json:"maskedKey"`
			Models    []ModelStat `json:"models"`
		}

		details := make([]APIKeyDetail, 0, len(allStats))

		for _, aks := range allStats {
			if filterKeyID != "" && aks.KeyID != filterKeyID {
				continue
			}

			modelsStats := aks.Stats()

			modelStats := make([]ModelStat, 0, len(modelsStats))

			for fullModelName, stat := range modelsStats {
				var inputTokens, cachedInputTokens, outputTokens, duration uint64

				inputTokens = stat[metrics.TimeWindow1Week].Input
				cachedInputTokens = stat[metrics.TimeWindow1Week].CachedInput
				outputTokens = stat[metrics.TimeWindow1Week].Output
				duration = stat[metrics.TimeWindow1Week].Duration

				var tokenSpeed float64
				if duration > 0 {
					durationSeconds := float64(duration) / 1000.0
					tokenSpeed = float64(outputTokens) / durationSeconds
				}

				modelStats = append(modelStats, ModelStat{
					Model:             fullModelName,
					ModelName:         extractModelName(fullModelName),
					Provider:          extractProviderName(fullModelName),
					Count6h:           stat[metrics.TimeWindow6Hours].Count,
					Count12h:          stat[metrics.TimeWindow12Hours].Count,
					Count1d:           stat[metrics.TimeWindow1Day].Count,
					Count3d:           stat[metrics.TimeWindow3Days].Count,
					Count1w:           stat[metrics.TimeWindow1Week].Count,
					InputTokens:       inputTokens,
					CachedInputTokens: cachedInputTokens,
					OutputTokens:      outputTokens,
					TokenSpeed:        tokenSpeed,
				})
			}

			sort.Slice(modelStats, func(i, j int) bool {
				return modelStats[i].Model < modelStats[j].Model
			})

			details = append(details, APIKeyDetail{
				KeyID:     aks.KeyID,
				MaskedKey: aks.MaskedKey,
				Models:    modelStats,
			})
		}

		sort.Slice(details, func(i, j int) bool {
			return details[i].KeyID < details[j].KeyID
		})

		c.JSON(http.StatusOK, gin.H{"stats": details})
	}
}

// NewLLMStatsTimeSeriesHandler creates a handler that returns time-series data for visualization.
// Query parameter "window" specifies the time window (6h, 12h, 1d, 3d, 1w). Defaults to 12h.
// Query parameter "keyId" filters data for a specific API key.
// Query parameter "model" filters data for a specific model.
// Query parameter "provider" filters data for a specific provider.
func NewLLMStatsTimeSeriesHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		windowParam := c.DefaultQuery("window", "12h")

		var timeWindow metrics.TimeWindow
		switch windowParam {
		case "6h":
			timeWindow = metrics.TimeWindow6Hours
		case "12h":
			timeWindow = metrics.TimeWindow12Hours
		case "1d":
			timeWindow = metrics.TimeWindow1Day
		case "3d":
			timeWindow = metrics.TimeWindow3Days
		case "1w":
			timeWindow = metrics.TimeWindow1Week
		default:
			timeWindow = metrics.TimeWindow12Hours
		}

		collector := metrics.DefaultTimeSeriesQuerier()
		if collector == nil {
			c.JSON(http.StatusOK, gin.H{"interval": timeSeriesInterval(timeWindow), "data": []struct{}{}})
			return
		}

		dataPoints := collector.QueryTimeSeries(timeWindow)

		type DataPoint struct {
			Timestamp int64  `json:"timestamp"`
			Count     uint64 `json:"count"`
			Input     uint64 `json:"input"`
			Cached    uint64 `json:"cached"`
			Output    uint64 `json:"output"`
		}

		type LLMTimeSeriesResponse struct {
			Interval int         `json:"interval"`
			Data     []DataPoint `json:"data"`
		}

		result := make([]DataPoint, len(dataPoints))
		for i, dp := range dataPoints {
			result[i] = DataPoint{
				Timestamp: dp.Timestamp,
				Count:     dp.Count,
				Input:     dp.Input,
				Cached:    dp.Cached,
				Output:    dp.Output,
			}
		}

		c.JSON(http.StatusOK, LLMTimeSeriesResponse{
			Interval: timeSeriesInterval(timeWindow),
			Data:     result,
		})
	}
}

// NewLLMServiceStatusHandler creates a handler that returns current service health status.
func NewLLMServiceStatusHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		collector := metrics.DefaultServiceStatusQuerier()
		if collector == nil {
			c.JSON(http.StatusOK, metrics.ServiceStatus{
				Status:  "healthy",
				Message: "no stats collector configured",
			})
			return
		}
		status := collector.QueryServiceStatus()
		c.JSON(http.StatusOK, status)
	}
}

// NewLLMErrorSummaryHandler creates a handler that returns error statistics summary.
func NewLLMErrorSummaryHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		collector := metrics.DefaultErrorQuerier()
		if collector == nil {
			c.JSON(http.StatusOK, gin.H{
				"lastErrorTime":   int64(0),
				"total1h":         0,
				"total6h":         0,
				"total12h":        0,
				"total24h":        0,
				"total1w":         0,
				"requestCount1h":  0,
				"requestCount6h":  0,
				"requestCount12h": 0,
				"requestCount24h": 0,
				"requestCount1w":  0,
				"errorTypes":      []struct{}{},
			})
			return
		}

		summary := collector.QueryErrorSummary()

		type ErrorTypeStats struct {
			Type        string `json:"type"`
			Label       string `json:"label"`
			Color       string `json:"color"`
			Description string `json:"description"`
			Count1h     uint64 `json:"count1h"`
			Count6h     uint64 `json:"count6h"`
			Count12h    uint64 `json:"count12h"`
			Count24h    uint64 `json:"count24h"`
			Count1w     uint64 `json:"count1w"`
		}

		type ErrorSummaryResponse struct {
			LastErrorTime   int64            `json:"lastErrorTime"`
			Total1h         uint64           `json:"total1h"`
			Total6h         uint64           `json:"total6h"`
			Total12h        uint64           `json:"total12h"`
			Total24h        uint64           `json:"total24h"`
			Total1w         uint64           `json:"total1w"`
			RequestCount1h  uint64           `json:"requestCount1h"`
			RequestCount6h  uint64           `json:"requestCount6h"`
			RequestCount12h uint64           `json:"requestCount12h"`
			RequestCount24h uint64           `json:"requestCount24h"`
			RequestCount1w  uint64           `json:"requestCount1w"`
			ErrorTypes      []ErrorTypeStats `json:"errorTypes"`
		}

		allTypes := metrics.AllErrorTypes()
		errorTypes := make([]ErrorTypeStats, len(allTypes))
		for i, typeInfo := range allTypes {
			errorTypes[i] = ErrorTypeStats{
				Type:        string(typeInfo.Type),
				Label:       typeInfo.Label,
				Color:       typeInfo.Color,
				Description: typeInfo.Description,
				Count1h:     summary.ByType1h[typeInfo.Type],
				Count6h:     summary.ByType6h[typeInfo.Type],
				Count12h:    summary.ByType12h[typeInfo.Type],
				Count24h:    summary.ByType24h[typeInfo.Type],
				Count1w:     summary.ByType1w[typeInfo.Type],
			}
		}

		c.JSON(http.StatusOK, ErrorSummaryResponse{
			LastErrorTime:   summary.LastErrorTime,
			Total1h:         summary.Total1h,
			Total6h:         summary.Total6h,
			Total12h:        summary.Total12h,
			Total24h:        summary.Total24h,
			Total1w:         summary.Total1w,
			RequestCount1h:  summary.RequestCount1h,
			RequestCount6h:  summary.RequestCount6h,
			RequestCount12h: summary.RequestCount12h,
			RequestCount24h: summary.RequestCount24h,
			RequestCount1w:  summary.RequestCount1w,
			ErrorTypes:      errorTypes,
		})
	}
}

// NewLLMErrorTimeSeriesHandler creates a handler that returns error time-series data.
// Query parameter "window" specifies the time window (6h, 12h, 1d, 3d, 1w). Defaults to 1d.
func NewLLMErrorTimeSeriesHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		collector := metrics.DefaultErrorQuerier()
		if collector == nil {
			c.JSON(http.StatusOK, gin.H{"interval": 300, "data": []struct{}{}})
			return
		}

		windowParam := c.DefaultQuery("window", "1d")

		var timeWindow metrics.TimeWindow
		switch windowParam {
		case "6h":
			timeWindow = metrics.TimeWindow6Hours
		case "12h":
			timeWindow = metrics.TimeWindow12Hours
		case "1d":
			timeWindow = metrics.TimeWindow1Day
		case "3d":
			timeWindow = metrics.TimeWindow3Days
		case "1w":
			timeWindow = metrics.TimeWindow1Week
		default:
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid time window, must be one of: 6h, 12h, 1d, 3d, 1w"})
			return
		}

		dataPoints := collector.QueryErrorTimeSeries(timeWindow)

		var interval int
		switch timeWindow {
		case metrics.TimeWindow6Hours, metrics.TimeWindow12Hours, metrics.TimeWindow1Day:
			interval = 300
		case metrics.TimeWindow3Days:
			interval = 900
		case metrics.TimeWindow1Week:
			interval = 1800
		}

		type ErrorDataPoint struct {
			Timestamp int64             `json:"timestamp"`
			Errors    map[string]uint64 `json:"errors"`
			Total     uint64            `json:"total"`
		}

		type LLMErrorTimeSeriesResponse struct {
			Interval int              `json:"interval"`
			Data     []ErrorDataPoint `json:"data"`
		}

		result := make([]ErrorDataPoint, len(dataPoints))
		for i, dp := range dataPoints {
			errors := make(map[string]uint64)
			for errorType, count := range dp.Errors {
				errors[string(errorType)] = count
			}
			result[i] = ErrorDataPoint{
				Timestamp: dp.Timestamp,
				Errors:    errors,
				Total:     dp.Total,
			}
		}

		c.JSON(http.StatusOK, LLMErrorTimeSeriesResponse{
			Interval: interval,
			Data:     result,
		})
	}
}
