package metrics

import (
	"fmt"
	"regexp"
	"time"
)

// StatsSummary returns cached per-key and per-model request statistics.
func (mc *MemoryCollector) StatsSummary() map[string]*APIKeyStats {
	mc.mu.RLock()
	if mc.summaryCache != nil {
		if data, ok := mc.summaryCache.get(mc.cacheTTL); ok {
			mc.mu.RUnlock()
			return data
		}
	}
	mc.mu.RUnlock()

	result := mc.computeStatsSummary()

	mc.mu.Lock()
	mc.summaryCache = &cacheEntry[map[string]*APIKeyStats]{data: result, cachedAt: time.Now()}
	mc.mu.Unlock()

	return result
}

func (mc *MemoryCollector) computeStatsSummary() map[string]*APIKeyStats {
	mc.mu.RLock()
	defer mc.mu.RUnlock()

	result := make(map[string]*APIKeyStats, len(mc.requestBuckets))

	for keyID, models := range mc.requestBuckets {
		maskedKey := mc.keyMaskMap[keyID]
		if maskedKey == "" {
			maskedKey = shortKeyID(keyID)
		}

		aks := &APIKeyStats{
			KeyID:      keyID,
			MaskedKey:  maskedKey,
			ModelStats: make(map[string]*ModelStats, len(models)),
		}
		result[keyID] = aks

		for model, buckets := range models {
			counters := make([]TimeCounter, 0, len(buckets))
			for ts, b := range buckets {
				counters = append(counters, TimeCounter{
					Timestamp:        time.Unix(ts, 0),
					Count:            b.Count,
					InputToken:       b.InputToken,
					CachedInputToken: b.CachedInputToken,
					OutputToken:      b.OutputToken,
					Duration:         b.Duration,
				})
			}
			aks.ModelStats[model] = &ModelStats{Counters: counters}
		}
	}

	return result
}

// QueryTimeSeries returns aggregate request time series data for a window.
func (mc *MemoryCollector) QueryTimeSeries(timeWindow TimeWindow) []TimeSeriesDataPoint {
	return mc.queryTimeSeriesCached(fmt.Sprintf("ts:%d", timeWindow), timeWindow, "", "", "")
}

// QueryTimeSeriesByKey returns request time series data for a key ID.
func (mc *MemoryCollector) QueryTimeSeriesByKey(timeWindow TimeWindow, keyID string) []TimeSeriesDataPoint {
	return mc.queryTimeSeriesCached(fmt.Sprintf("ts_key:%s:%d", keyID, timeWindow), timeWindow, keyID, "", "")
}

// QueryTimeSeriesByModel returns request time series data for a model name.
func (mc *MemoryCollector) QueryTimeSeriesByModel(timeWindow TimeWindow, model string) []TimeSeriesDataPoint {
	return mc.queryTimeSeriesCached(fmt.Sprintf("ts_model:%s:%d", model, timeWindow), timeWindow, "", model, "")
}

// QueryTimeSeriesByProvider returns request time series data for models under a provider prefix.
func (mc *MemoryCollector) QueryTimeSeriesByProvider(timeWindow TimeWindow, provider string) []TimeSeriesDataPoint {
	return mc.queryTimeSeriesCached(fmt.Sprintf("ts_p:%s:%d", provider, timeWindow), timeWindow, "", "", provider)
}

func (mc *MemoryCollector) queryTimeSeriesCached(
	cacheKey string, timeWindow TimeWindow, keyID, model, provider string,
) []TimeSeriesDataPoint {

	mc.mu.RLock()
	if entry, exists := mc.tsCache[cacheKey]; exists && entry != nil {
		if data, ok := entry.get(mc.cacheTTL); ok {
			mc.mu.RUnlock()
			return data
		}
	}
	mc.mu.RUnlock()

	result := mc.computeTimeSeries(timeWindow, keyID, model, provider)

	now := time.Now()
	mc.mu.Lock()
	mc.pruneTimeSeriesCacheLocked(now)
	mc.tsCache[cacheKey] = &cacheEntry[[]TimeSeriesDataPoint]{data: result, cachedAt: now}
	mc.mu.Unlock()

	return result
}

func (mc *MemoryCollector) pruneTimeSeriesCacheLocked(now time.Time) {
	for key, entry := range mc.tsCache {
		if entry == nil || !now.Before(entry.cachedAt.Add(mc.cacheTTL)) {
			delete(mc.tsCache, key)
		}
	}
	for len(mc.tsCache) >= maxTimeSeriesCacheEntries {
		oldestKey := ""
		var oldestAt time.Time
		for key, entry := range mc.tsCache {
			if entry == nil {
				oldestKey = key
				break
			}
			if oldestKey == "" || entry.cachedAt.Before(oldestAt) {
				oldestKey = key
				oldestAt = entry.cachedAt
			}
		}
		if oldestKey == "" {
			return
		}
		delete(mc.tsCache, oldestKey)
	}
}

func (mc *MemoryCollector) computeTimeSeries(
	timeWindow TimeWindow, keyID, model, provider string,
) []TimeSeriesDataPoint {

	mc.mu.RLock()
	defer mc.mu.RUnlock()

	interval := aggregationInterval(timeWindow)
	now := time.Now()
	cutoffTs := now.Add(-timeWindow.Duration()).Truncate(interval).Unix()
	lastBucketTs := now.Truncate(interval).Unix()
	rawCutoffTs := now.Add(-timeWindow.Duration()).Truncate(bucketGranularity).Unix()
	rawNowTs := now.Unix()

	var modelRegex *regexp.Regexp
	if model != "" {
		modelRegex = regexp.MustCompile("^(|^.*/)" + regexp.QuoteMeta(model) + "$")
	}
	var providerRegex *regexp.Regexp
	if provider != "" {
		providerRegex = regexp.MustCompile("^" + regexp.QuoteMeta(provider) + "/.*")
	}

	bucketMap := make(map[int64]*TimeSeriesDataPoint)

	for kid, models := range mc.requestBuckets {
		if keyID != "" && kid != keyID {
			continue
		}
		for mdl, buckets := range models {
			if modelRegex != nil && !modelRegex.MatchString(mdl) {
				continue
			}
			if providerRegex != nil && !providerRegex.MatchString(mdl) {
				continue
			}
			for ts, b := range buckets {
				if ts < rawCutoffTs || ts > rawNowTs {
					continue
				}
				// Re-bucket raw 5-minute timestamps into the coarser
				// aggregation interval so that the output agentteam (which
				// steps by interval) finds every data point.
				coarseTs := time.Unix(ts, 0).Truncate(interval).Unix()
				dp, exists := bucketMap[coarseTs]
				if !exists {
					dp = &TimeSeriesDataPoint{Timestamp: coarseTs}
					bucketMap[coarseTs] = dp
				}
				dp.Count += b.Count
				dp.Input += b.InputToken
				dp.Cached += b.CachedInputToken
				dp.Output += b.OutputToken
			}
		}
	}

	result := make([]TimeSeriesDataPoint, 0, (lastBucketTs-cutoffTs)/int64(interval.Seconds())+1)
	for ts := cutoffTs; ts <= lastBucketTs; ts += int64(interval.Seconds()) {
		if dp, exists := bucketMap[ts]; exists {
			result = append(result, *dp)
		} else {
			result = append(result, TimeSeriesDataPoint{Timestamp: ts})
		}
	}

	return result
}

// QueryErrorTimeSeries returns cached error counts grouped by error type.
func (mc *MemoryCollector) QueryErrorTimeSeries(timeWindow TimeWindow) []ErrorTimeSeriesDataPoint {
	mc.mu.RLock()
	if entry, exists := mc.errorTsCache[timeWindow]; exists && entry != nil {
		if data, ok := entry.get(mc.cacheTTL); ok {
			mc.mu.RUnlock()
			return data
		}
	}
	mc.mu.RUnlock()

	result := mc.computeErrorTimeSeries(timeWindow)

	mc.mu.Lock()
	mc.errorTsCache[timeWindow] = &cacheEntry[[]ErrorTimeSeriesDataPoint]{data: result, cachedAt: time.Now()}
	mc.mu.Unlock()

	return result
}

func (mc *MemoryCollector) computeErrorTimeSeries(timeWindow TimeWindow) []ErrorTimeSeriesDataPoint {
	mc.mu.RLock()
	defer mc.mu.RUnlock()

	interval := 5 * time.Minute
	now := time.Now()
	cutoffTs := now.Add(-timeWindow.Duration()).Truncate(interval).Unix()
	lastBucketTs := now.Truncate(interval).Unix()

	bucketMap := make(map[int64]map[ErrorType]uint64)

	for ts, errors := range mc.errorBuckets {
		if ts < cutoffTs || ts > lastBucketTs {
			continue
		}
		if bucketMap[ts] == nil {
			bucketMap[ts] = make(map[ErrorType]uint64)
		}
		for et, count := range errors {
			bucketMap[ts][et] += count
		}
	}

	result := make([]ErrorTimeSeriesDataPoint, 0, (lastBucketTs-cutoffTs)/int64(interval.Seconds())+1)
	for ts := cutoffTs; ts <= lastBucketTs; ts += int64(interval.Seconds()) {
		errorsByType := bucketMap[ts]
		if errorsByType == nil {
			errorsByType = make(map[ErrorType]uint64)
		}
		var total uint64
		for _, count := range errorsByType {
			total += count
		}
		result = append(result, ErrorTimeSeriesDataPoint{
			Timestamp: ts,
			Errors:    errorsByType,
			Total:     total,
		})
	}

	return result
}

// QueryErrorSummary returns cached rolling error totals and recent request counts.
func (mc *MemoryCollector) QueryErrorSummary() ErrorSummary {
	mc.mu.RLock()
	if mc.errorSummaryCache != nil {
		if data, ok := mc.errorSummaryCache.get(mc.cacheTTL); ok {
			mc.mu.RUnlock()
			return data
		}
	}
	mc.mu.RUnlock()

	result := mc.computeErrorSummary()

	mc.mu.Lock()
	mc.errorSummaryCache = &cacheEntry[ErrorSummary]{data: result, cachedAt: time.Now()}
	mc.mu.Unlock()

	return result
}

func (mc *MemoryCollector) computeErrorSummary() ErrorSummary {
	mc.mu.RLock()
	defer mc.mu.RUnlock()

	now := time.Now()
	nowUTC := now.UTC()

	summary := ErrorSummary{
		ByType1h:      make(map[ErrorType]uint64),
		ByType6h:      make(map[ErrorType]uint64),
		ByType12h:     make(map[ErrorType]uint64),
		ByType24h:     make(map[ErrorType]uint64),
		ByType1w:      make(map[ErrorType]uint64),
		LastErrorTime: mc.lastErrorTime,
	}

	for ts, errors := range mc.errorBuckets {
		tsUTC := time.Unix(ts, 0).UTC()
		for et, count := range errors {
			if tsUTC.After(nowUTC.Add(-1 * time.Hour)) {
				summary.Total1h += count
				summary.ByType1h[et] += count
			}
			if tsUTC.After(nowUTC.Add(-6 * time.Hour)) {
				summary.Total6h += count
				summary.ByType6h[et] += count
			}
			if tsUTC.After(nowUTC.Add(-12 * time.Hour)) {
				summary.Total12h += count
				summary.ByType12h[et] += count
			}
			if tsUTC.After(nowUTC.Add(-24 * time.Hour)) {
				summary.Total24h += count
				summary.ByType24h[et] += count
			}
			if tsUTC.After(nowUTC.Add(-7 * 24 * time.Hour)) {
				summary.Total1w += count
				summary.ByType1w[et] += count
			}
		}
	}

	for _, models := range mc.requestBuckets {
		for _, buckets := range models {
			for ts, b := range buckets {
				tsUTC := time.Unix(ts, 0).UTC()
				if tsUTC.After(nowUTC.Add(-1 * time.Hour)) {
					summary.RequestCount1h += b.Count
				}
				if tsUTC.After(nowUTC.Add(-6 * time.Hour)) {
					summary.RequestCount6h += b.Count
				}
				if tsUTC.After(nowUTC.Add(-12 * time.Hour)) {
					summary.RequestCount12h += b.Count
				}
				if tsUTC.After(nowUTC.Add(-24 * time.Hour)) {
					summary.RequestCount24h += b.Count
				}
				if tsUTC.After(nowUTC.Add(-7 * 24 * time.Hour)) {
					summary.RequestCount1w += b.Count
				}
			}
		}
	}

	return summary
}

// QueryServiceStatus derives a coarse health status from recent error metrics.
func (mc *MemoryCollector) QueryServiceStatus() ServiceStatus {
	summary := mc.QueryErrorSummary()

	status := ServiceStatus{
		Status:  "healthy",
		Message: "Service is operating normally",
	}

	if summary.LastErrorTime > 0 {
		lastErrorTime := time.Unix(summary.LastErrorTime, 0)
		status.LastErrorAgo = int64(time.Since(lastErrorTime).Seconds())

		if summary.Total1h > 10 {
			status.Status = "unhealthy"
			status.Message = "High error rate detected in the last hour"
		} else if summary.Total1h > 0 || summary.Total6h > 20 {
			status.Status = "degraded"
			status.Message = "Some errors detected recently"
		} else if status.LastErrorAgo < 3600 {
			status.Status = "degraded"
			status.Message = "Recent errors detected"
		}
	}

	return status
}
