// Package metrics provides usage statistics tracking and Prometheus TSDB storage.
package metrics

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// init initializes the stats directory to a user-writable location
func init() {
	home, err := os.UserHomeDir()
	if err != nil {
		home = os.TempDir()
	}

	statsDir = filepath.Join(home, ".agentteam", "metrics")
	tsdbPath = filepath.Join(statsDir, "stats.tsdb")
}

// ============================================================================
// Configuration
// ============================================================================

var (
	statsDir string
	// tsdbPath is the path to the TSDB database
	tsdbPath string
)

const (
	// bucketGranularity is the granularity for time buckets (5 minutes)
	bucketGranularity = 5 * time.Minute
)

// ============================================================================
// API Key Utilities
// ============================================================================

// KeyID generates a unique identifier for an API key using SHA256 hash.
func KeyID(apiKey string) string {
	hash := sha256.Sum256([]byte(apiKey))
	return hex.EncodeToString(hash[:])
}

// MaskAPIKey returns the stable display form for an API key.
// Keys with ten or fewer characters keep their full value and append the mask
// suffix; longer keys keep only the first ten characters before the suffix.
func MaskAPIKey(apiKey string) string {
	if len(apiKey) <= 10 {
		return apiKey + "****"
	}
	return apiKey[:10] + "****"
}

// ============================================================================
// Time Window Types
// ============================================================================

// TimeWindow defines a statistics aggregation time window
type TimeWindow int

const (
	TimeWindow6Hours TimeWindow = iota
	TimeWindow12Hours
	TimeWindow1Day
	TimeWindow3Days
	TimeWindow1Week
)

// Duration returns the time duration for this time window
func (tw TimeWindow) Duration() time.Duration {
	switch tw {
	case TimeWindow6Hours:
		return 6 * time.Hour
	case TimeWindow12Hours:
		return 12 * time.Hour
	case TimeWindow1Day:
		return 24 * time.Hour
	case TimeWindow3Days:
		return 3 * 24 * time.Hour
	case TimeWindow1Week:
		return 7 * 24 * time.Hour
	default:
		return 6 * time.Hour
	}
}

// String returns a human-readable string representation
func (tw TimeWindow) String() string {
	switch tw {
	case TimeWindow6Hours:
		return "6h"
	case TimeWindow12Hours:
		return "12h"
	case TimeWindow1Day:
		return "1d"
	case TimeWindow3Days:
		return "3d"
	case TimeWindow1Week:
		return "1w"
	default:
		return "unknown"
	}
}

// ============================================================================
// Error Types
// ============================================================================

// ErrorType represents the category of an error
type ErrorType string

const (
	// ErrorTypeAuth represents authentication/authorization errors (401, 403)
	ErrorTypeAuth ErrorType = "auth"
	// ErrorTypeRateLimit represents rate limiting errors (429)
	ErrorTypeRateLimit ErrorType = "rate_limit"
	// ErrorTypeNetwork represents network connectivity errors
	ErrorTypeNetwork ErrorType = "network"
	// ErrorTypeProvider represents upstream provider errors (5xx from provider)
	ErrorTypeProvider ErrorType = "provider"
	// ErrorTypeBadRequest represents bad request errors (400)
	ErrorTypeBadRequest ErrorType = "bad_request"
	// ErrorTypeModelNotFound represents model not found errors
	ErrorTypeModelNotFound ErrorType = "model_not_found"
	// ErrorTypeInternal represents internal server errors
	ErrorTypeInternal ErrorType = "internal"
	// ErrorTypeTimeout represents request timeout errors
	ErrorTypeTimeout ErrorType = "timeout"
)

// String returns a human-readable string for the error type
func (et ErrorType) String() string {
	return string(et)
}

// ErrorTypeInfo contains display information for an error type
type ErrorTypeInfo struct {
	Type        ErrorType `json:"type"`
	Label       string    `json:"label"`
	Color       string    `json:"color"`
	Description string    `json:"description"`
}

// AllErrorTypes returns all defined error types with their display info
func AllErrorTypes() []ErrorTypeInfo {
	return []ErrorTypeInfo{
		{ErrorTypeAuth, "Authentication Error", "#ef4444", "API key invalid or access denied"},
		{ErrorTypeRateLimit, "Rate Limit", "#f97316", "Too many requests"},
		{ErrorTypeNetwork, "Network Error", "#8b5cf6", "Connection failed"},
		{ErrorTypeProvider, "Provider Error", "#ec4899", "Upstream provider error"},
		{ErrorTypeBadRequest, "Bad Request", "#eab308", "Invalid request format"},
		{ErrorTypeModelNotFound, "Model Not Found", "#6366f1", "Model does not exist"},
		{ErrorTypeInternal, "Internal Error", "#dc2626", "Internal server error"},
		{ErrorTypeTimeout, "Timeout", "#f59e0b", "Request timed out"},
	}
}

// ============================================================================
// Core Data Structures
// ============================================================================

// WindowStats represents statistics for a specific time window.
type WindowStats struct {
	Count       uint64
	Input       uint64
	CachedInput uint64
	Output      uint64
	Duration    uint64
}

// TimeSeriesDataPoint represents a single data point in the time series.
type TimeSeriesDataPoint struct {
	Timestamp int64  `json:"timestamp"`
	Count     uint64 `json:"count"`
	Input     uint64 `json:"input"`
	Cached    uint64 `json:"cached"`
	Output    uint64 `json:"output"`
}

// TokenUsageStats tracks request and token totals for one aggregation bucket.
type TokenUsageStats struct {
	Count       uint64
	Input       uint64
	CachedInput uint64
	Output      uint64
}

// BarSeriesEntry describes one grouped or stacked bar series.
type BarSeriesEntry struct {
	Key   string `json:"key"`
	Label string `json:"label"`
}

// BarSeriesPoint represents one aggregated bar bucket keyed by series key.
type BarSeriesPoint struct {
	Timestamp int64             `json:"timestamp"`
	Values    map[string]uint64 `json:"values"`
}

// BarSeriesResponse is a generic bucketed bar chart payload.
type BarSeriesResponse struct {
	Interval int              `json:"interval"`
	Series   []BarSeriesEntry `json:"series"`
	Data     []BarSeriesPoint `json:"data"`
}

// ErrorTimeSeriesDataPoint represents a single error data point by type in the time series.
type ErrorTimeSeriesDataPoint struct {
	Timestamp int64                `json:"timestamp"`
	Errors    map[ErrorType]uint64 `json:"errors"` // Error counts by type
	Total     uint64               `json:"total"`  // Total errors across all types
}

// ErrorSummary represents error statistics summary.
type ErrorSummary struct {
	LastErrorTime int64                `json:"lastErrorTime"` // Unix timestamp of last error
	Total1h       uint64               `json:"total1h"`       // Total errors in last 1 hour
	Total6h       uint64               `json:"total6h"`       // Total errors in last 6 hours
	Total12h      uint64               `json:"total12h"`      // Total errors in last 12 hours
	Total24h      uint64               `json:"total24h"`      // Total errors in last 24 hours
	Total1w       uint64               `json:"total1w"`       // Total errors in last 1 week
	ByType1h      map[ErrorType]uint64 `json:"byType1h"`      // Errors by type in last 1 hour
	ByType6h      map[ErrorType]uint64 `json:"byType6h"`      // Errors by type in last 6 hours
	ByType12h     map[ErrorType]uint64 `json:"byType12h"`     // Errors by type in last 12 hours
	ByType24h     map[ErrorType]uint64 `json:"byType24h"`     // Errors by type in last 24 hours
	ByType1w      map[ErrorType]uint64 `json:"byType1w"`      // Errors by type in last 1 week
	// Request counts for calculating error rates
	RequestCount1h  uint64 `json:"requestCount1h"`  // Total requests in last 1 hour
	RequestCount6h  uint64 `json:"requestCount6h"`  // Total requests in last 6 hours
	RequestCount12h uint64 `json:"requestCount12h"` // Total requests in last 12 hours
	RequestCount24h uint64 `json:"requestCount24h"` // Total requests in last 24 hours
	RequestCount1w  uint64 `json:"requestCount1w"`  // Total requests in last 1 week
}

// ServiceStatus represents the current service health status.
type ServiceStatus struct {
	Status       string `json:"status"`       // "healthy", "degraded", "unhealthy"
	Message      string `json:"message"`      // Human-readable status message
	LastErrorAgo int64  `json:"lastErrorAgo"` // Seconds since last error (0 if no errors)
}

// TimeCounter tracks counts and tokens for a specific 5-minute time bucket.
type TimeCounter struct {
	Timestamp        time.Time
	Count            uint64
	InputToken       uint64
	CachedInputToken uint64
	OutputToken      uint64
	Duration         uint64
}

// ModelStats tracks statistics for a specific model using 5-minute buckets.
type ModelStats struct {
	mu       sync.RWMutex
	Counters []TimeCounter
}

// Stats returns all statistics for all time windows.
func (ms *ModelStats) Stats() map[TimeWindow]WindowStats {
	ms.mu.Lock()
	defer ms.mu.Unlock()

	now := time.Now()
	stats := make(map[TimeWindow]WindowStats)

	for tw := TimeWindow6Hours; tw <= TimeWindow1Week; tw++ {
		stats[tw] = WindowStats{}
	}

	cutoff := now.Add(-TimeWindow1Week.Duration())
	newCounters := make([]TimeCounter, 0, len(ms.Counters))

	for _, counter := range ms.Counters {
		if counter.Timestamp.Before(cutoff) {
			continue
		}
		newCounters = append(newCounters, counter)

		for tw := TimeWindow6Hours; tw <= TimeWindow1Week; tw++ {
			if counter.Timestamp.After(now.Add(-tw.Duration())) {
				current := stats[tw]
				current.Count += counter.Count
				current.Input += counter.InputToken
				current.CachedInput += counter.CachedInputToken
				current.Output += counter.OutputToken
				current.Duration += counter.Duration
				stats[tw] = current
			}
		}
	}

	ms.Counters = newCounters
	return stats
}

// APIKeyStats tracks statistics for a single API key across different models.
type APIKeyStats struct {
	mu         sync.RWMutex
	KeyID      string
	MaskedKey  string
	ModelStats map[string]*ModelStats
}

// Stats returns all statistics for all models across all time windows.
func (aks *APIKeyStats) Stats() map[string]map[TimeWindow]WindowStats {
	aks.mu.RLock()
	defer aks.mu.RUnlock()

	result := make(map[string]map[TimeWindow]WindowStats)

	for model, modelStats := range aks.ModelStats {
		stats := modelStats.Stats()
		modelResult := make(map[TimeWindow]WindowStats)

		for tw := TimeWindow6Hours; tw <= TimeWindow1Week; tw++ {
			modelResult[tw] = stats[tw]
		}

		// Skip model if all stats are zero
		allZero := true
		for _, stat := range modelResult {
			if stat.Count != 0 || stat.Input != 0 || stat.CachedInput != 0 || stat.Output != 0 || stat.Duration != 0 {
				allZero = false
				break
			}
		}
		if allZero {
			continue
		}

		result[model] = modelResult
	}

	return result
}

// ============================================================================
// Collector Interface
// ============================================================================

// RequestRecorder records successful LLM token usage.
type RequestRecorder interface {
	// RecordRequest records an access with model, token information, and duration.
	// Duration can be 0 if not tracking request latency.
	RecordRequest(apiKey, model string, inputToken, cachedInputToken, outputToken uint64, duration time.Duration)
}

// ErrorRecorder records LLM errors.
type ErrorRecorder interface {
	// RecordError records an error occurrence with the specified type and model.
	RecordError(model string, errorType ErrorType)
}

// StatsQuerier reads aggregate per-key/per-model LLM statistics.
type StatsQuerier interface {
	// StatsSummary returns statistics summary for all API keys.
	StatsSummary() map[string]*APIKeyStats
}

// TimeSeriesQuerier reads LLM request time-series data.
type TimeSeriesQuerier interface {
	// QueryTimeSeries retrieves time-series data for the specified time window.
	QueryTimeSeries(timeWindow TimeWindow) []TimeSeriesDataPoint
	// QueryTimeSeriesByKey retrieves time-series data for a specific API key.
	QueryTimeSeriesByKey(timeWindow TimeWindow, keyID string) []TimeSeriesDataPoint
	// QueryTimeSeriesByModel retrieves time-series data for a specific model across all keys.
	QueryTimeSeriesByModel(timeWindow TimeWindow, model string) []TimeSeriesDataPoint
	// QueryTimeSeriesByProvider retrieves time-series data for a specific provider across all keys.
	// It matches all models with the "provider/" prefix in the model label.
	QueryTimeSeriesByProvider(timeWindow TimeWindow, provider string) []TimeSeriesDataPoint
}

// ErrorQuerier reads LLM error aggregates and time-series data.
type ErrorQuerier interface {
	// QueryErrorTimeSeries retrieves error time-series data for the specified time window.
	QueryErrorTimeSeries(timeWindow TimeWindow) []ErrorTimeSeriesDataPoint
	// QueryErrorSummary returns error statistics summary.
	QueryErrorSummary() ErrorSummary
}

// ServiceStatusQuerier reads collector health state.
type ServiceStatusQuerier interface {
	// QueryServiceStatus returns the current service health status.
	QueryServiceStatus() ServiceStatus
}

// Lifecycle controls background collector processing and resource ownership.
type Lifecycle interface {
	// Start begins the collector (can be no-op for some implementations).
	Start(ctx context.Context)
	// Stop gracefully stops the collector.
	Stop()
	// Close closes the collector and releases resources.
	Close() error
}

// Collector is the full LLM metrics collector implementation.
// Prefer the narrower interfaces above at call sites.
type Collector interface {
	RequestRecorder
	ErrorRecorder
	StatsQuerier
	TimeSeriesQuerier
	ErrorQuerier
	ServiceStatusQuerier
	Lifecycle
}

// NewCollector creates a new statistics collector with in-memory aggregation
// and periodic TSDB flushing for persistence. It opens its own TSDB instance.
func NewCollector() (Collector, error) {
	return NewMemoryCollector()
}

// NewCollectorWithTSDB creates a new statistics collector that shares the given
// TSDBCollector. The caller is responsible for closing the TSDBCollector.
func NewCollectorWithTSDB(tsdb *TSDBCollector) Collector {
	return NewMemoryCollectorWithTSDB(tsdb)
}

// shortKeyID returns a short version of the keyID for display (first 12 chars)
func shortKeyID(keyID string) string {
	if len(keyID) <= 12 {
		return keyID
	}
	return keyID[:12]
}

// aggregationInterval returns the time bucket interval for a given time window.
func aggregationInterval(tw TimeWindow) time.Duration {
	switch tw {
	case TimeWindow6Hours:
		return 5 * time.Minute
	case TimeWindow12Hours:
		return 10 * time.Minute
	case TimeWindow1Day:
		return 15 * time.Minute
	case TimeWindow3Days:
		return 30 * time.Minute
	case TimeWindow1Week:
		return time.Hour
	default:
		return 5 * time.Minute
	}
}

// TimeSeriesAggregationInterval returns the bucket interval used by trend
// time-series data for the given time window.
func TimeSeriesAggregationInterval(tw TimeWindow) time.Duration {
	return aggregationInterval(tw)
}

// BarAggregationInterval returns the coarser bucket interval used by overview
// bar charts for the given time window.
func BarAggregationInterval(tw TimeWindow) time.Duration {
	switch tw {
	case TimeWindow6Hours:
		return 30 * time.Minute
	case TimeWindow12Hours:
		return time.Hour
	case TimeWindow1Day:
		return 2 * time.Hour
	case TimeWindow3Days:
		return 6 * time.Hour
	case TimeWindow1Week:
		return 12 * time.Hour
	default:
		return time.Hour
	}
}

// MachineDashboardAggregationInterval is the bucket interval for the Machine
// Dashboard's token-source breakdown — the finest granularity shown on the
// page. The overview trend/bar charts above it use the coarser
// MachineDashboardOverviewAggregationInterval (2x this), so the top-of-page
// charts use half as many buckets as the token-source detail beneath them.
func MachineDashboardAggregationInterval(tw TimeWindow) time.Duration {
	switch tw {
	case TimeWindow6Hours:
		return 10 * time.Minute
	case TimeWindow12Hours:
		return 20 * time.Minute
	case TimeWindow1Day:
		return 30 * time.Minute
	case TimeWindow3Days:
		return time.Hour
	case TimeWindow1Week:
		return 2 * time.Hour
	default:
		return 10 * time.Minute
	}
}

// MachineDashboardOverviewAggregationInterval is the bucket interval for the
// Machine Dashboard's overview trend and stacked-bar charts (message/token
// trends and by-model bars). It is intentionally 2x the token-source interval
// so the overview charts use coarser buckets than the detailed token-source
// breakdown below them (e.g. 1 day -> 1h here vs 30m for token source).
func MachineDashboardOverviewAggregationInterval(tw TimeWindow) time.Duration {
	return 2 * MachineDashboardAggregationInterval(tw)
}
