package metrics

import (
	"context"
	"log/slog"
	"time"

	"github.com/prometheus/prometheus/model/labels"
	"github.com/prometheus/prometheus/tsdb/chunkenc"
)

// ============================================================================
// Error Statistics Methods
// ============================================================================

// RecordError records an error occurrence with the specified type and model.
func (tc *TSDBCollector) RecordError(model string, errorType ErrorType) {
	timestamp := time.Now().UnixMilli()

	if tc.db == nil {
		slog.Warn("TSDB not available, skipping error recording")
		return
	}

	ctx := context.Background()
	app := tc.db.Appender(ctx)

	errorLabels := labels.FromStrings(
		"__name__", metricErrorsTotal,
		labelModel, model,
		labelErrorType, string(errorType),
	)

	if _, err := app.Append(0, errorLabels, timestamp, 1.0); err != nil {
		slog.Warn("Failed to append error count", "error", err)
	}

	if err := app.Commit(); err != nil {
		slog.Warn("Failed to commit error stats to TSDB", "error", err)
	}
}

// QueryErrorTimeSeries retrieves error time-series data for the specified time window.
func (tc *TSDBCollector) QueryErrorTimeSeries(timeWindow TimeWindow) []ErrorTimeSeriesDataPoint {
	tc.mu.RLock()
	defer tc.mu.RUnlock()

	if tc.db == nil {
		return []ErrorTimeSeriesDataPoint{}
	}

	// Use 5-minute aggregation for all time windows
	aggregationInterval := 5 * time.Minute

	now := time.Now()
	windowDuration := timeWindow.Duration()
	mint := now.Add(-windowDuration).UnixMilli()
	maxt := now.UnixMilli()

	ctx := context.Background()
	querier, err := tc.db.Querier(mint, maxt)
	if err != nil {
		slog.Warn("Failed to create querier for error time series", "error", err)
		return []ErrorTimeSeriesDataPoint{}
	}
	defer querier.Close()

	// Map: timestamp -> error type -> count
	bucketMap := make(map[int64]map[ErrorType]uint64)

	matcher := labels.MustNewMatcher(labels.MatchEqual, "__name__", metricErrorsTotal)
	seriesSet := querier.Select(ctx, false, nil, matcher)

	for seriesSet.Next() {
		series := seriesSet.At()
		lbls := series.Labels()
		errorTypeStr := lbls.Get(labelErrorType)
		if errorTypeStr == "" {
			continue
		}
		errorType := ErrorType(errorTypeStr)

		iterator := series.Iterator(nil)
		for iterator.Next() == chunkenc.ValFloat {
			t, v := iterator.At()
			bucketTime := time.UnixMilli(t).Truncate(aggregationInterval)
			timestamp := bucketTime.Unix()

			if bucketMap[timestamp] == nil {
				bucketMap[timestamp] = make(map[ErrorType]uint64)
			}
			bucketMap[timestamp][errorType] += uint64(v)
		}

		if err := iterator.Err(); err != nil {
			slog.Warn("Error iterating error time series samples", "error", err)
		}
	}

	if err := seriesSet.Err(); err != nil {
		slog.Warn("Error iterating error time series", "error", err)
	}

	// Generate all buckets in the time window
	cutoff := now.Add(-windowDuration)
	firstBucket := cutoff.Truncate(aggregationInterval)
	lastBucket := now.Truncate(aggregationInterval)

	var result []ErrorTimeSeriesDataPoint
	for t := firstBucket; !t.After(lastBucket); t = t.Add(aggregationInterval) {
		timestamp := t.Unix()
		errorsByType, exists := bucketMap[timestamp]
		var total uint64
		for _, count := range errorsByType {
			total += count
		}
		if !exists {
			errorsByType = make(map[ErrorType]uint64)
		}
		result = append(result, ErrorTimeSeriesDataPoint{
			Timestamp: timestamp,
			Errors:    errorsByType,
			Total:     total,
		})
	}

	return result
}

// QueryErrorSummary returns error statistics summary.
func (tc *TSDBCollector) QueryErrorSummary() ErrorSummary {
	tc.mu.RLock()
	defer tc.mu.RUnlock()

	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),
	}

	if tc.db == nil {
		return summary
	}

	now := time.Now()
	// Use UTC for consistent timezone handling with TSDB timestamps
	nowUTC := now.UTC()
	// Query 1 week of data to cover all time windows
	mint := nowUTC.Add(-7 * 24 * time.Hour).UnixMilli()
	maxt := now.UnixMilli()

	ctx := context.Background()
	querier, err := tc.db.Querier(mint, maxt)
	if err != nil {
		slog.Warn("Failed to create querier for error summary", "error", err)
		return summary
	}
	defer querier.Close()

	// Query error counts
	matcher := labels.MustNewMatcher(labels.MatchEqual, "__name__", metricErrorsTotal)
	seriesSet := querier.Select(ctx, false, nil, matcher)

	for seriesSet.Next() {
		series := seriesSet.At()
		lbls := series.Labels()
		errorTypeStr := lbls.Get(labelErrorType)
		if errorTypeStr == "" {
			continue
		}
		errorType := ErrorType(errorTypeStr)

		iterator := series.Iterator(nil)
		for iterator.Next() == chunkenc.ValFloat {
			t, v := iterator.At()
			errorTime := time.UnixMilli(t)
			count := uint64(v)

			// Track last error time
			if errorTime.Unix() > summary.LastErrorTime {
				summary.LastErrorTime = errorTime.Unix()
			}

			// Aggregate by time window
			errorTimeUTC := errorTime.UTC()
			if errorTimeUTC.After(nowUTC.Add(-1 * time.Hour)) {
				summary.Total1h += count
				summary.ByType1h[errorType] += count
			}
			if errorTimeUTC.After(nowUTC.Add(-6 * time.Hour)) {
				summary.Total6h += count
				summary.ByType6h[errorType] += count
			}
			if errorTimeUTC.After(nowUTC.Add(-12 * time.Hour)) {
				summary.Total12h += count
				summary.ByType12h[errorType] += count
			}
			if errorTimeUTC.After(nowUTC.Add(-24 * time.Hour)) {
				summary.Total24h += count
				summary.ByType24h[errorType] += count
			}
			if errorTimeUTC.After(nowUTC.Add(-7 * 24 * time.Hour)) {
				summary.Total1w += count
				summary.ByType1w[errorType] += count
			}
		}

		if err := iterator.Err(); err != nil {
			slog.Warn("Error iterating error summary samples", "error", err)
		}
	}

	if err := seriesSet.Err(); err != nil {
		slog.Warn("Error iterating error summary series", "error", err)
	}

	// Query request counts for calculating error rates
	reqMatcher := labels.MustNewMatcher(labels.MatchEqual, "__name__", metricRequestsTotal)
	reqSeriesSet := querier.Select(ctx, false, nil, reqMatcher)

	for reqSeriesSet.Next() {
		series := reqSeriesSet.At()
		iterator := series.Iterator(nil)
		for iterator.Next() == chunkenc.ValFloat {
			t, v := iterator.At()
			reqTime := time.UnixMilli(t)
			count := uint64(v)

			// Aggregate by time window
			reqTimeUTC := reqTime.UTC()
			if reqTimeUTC.After(nowUTC.Add(-1 * time.Hour)) {
				summary.RequestCount1h += count
			}
			if reqTimeUTC.After(nowUTC.Add(-6 * time.Hour)) {
				summary.RequestCount6h += count
			}
			if reqTimeUTC.After(nowUTC.Add(-12 * time.Hour)) {
				summary.RequestCount12h += count
			}
			if reqTimeUTC.After(nowUTC.Add(-24 * time.Hour)) {
				summary.RequestCount24h += count
			}
			if reqTimeUTC.After(nowUTC.Add(-7 * 24 * time.Hour)) {
				summary.RequestCount1w += count
			}
		}

		if err := iterator.Err(); err != nil {
			slog.Warn("Error iterating request count samples", "error", err)
		}
	}

	if err := reqSeriesSet.Err(); err != nil {
		slog.Warn("Error iterating request count series", "error", err)
	}

	return summary
}

// QueryServiceStatus returns the current service health status.
func (tc *TSDBCollector) QueryServiceStatus() ServiceStatus {
	summary := tc.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())

		// Determine status based on recent error rate
		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 {
			// Error within last hour but low count
			status.Status = "degraded"
			status.Message = "Recent errors detected"
		}
	}

	return status
}
