package metrics

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Metrics holds all Prometheus metrics for the gateway
type Metrics struct {
	// Request metrics
	RequestsTotal     *prometheus.CounterVec
	RequestDuration   *prometheus.HistogramVec
	ActiveConnections prometheus.Gauge

	// Upload metrics
	UploadsTotal      *prometheus.CounterVec
	UploadSize        *prometheus.HistogramVec
	UploadDuration    *prometheus.HistogramVec

	// Download metrics
	DownloadsTotal    *prometheus.CounterVec
	DownloadSize      *prometheus.HistogramVec
	DownloadDuration  *prometheus.HistogramVec

	// Stream metrics
	StreamsActive     prometheus.Gauge
	StreamsTotal      *prometheus.CounterVec
	StreamDuration    *prometheus.HistogramVec

	// Storage metrics
	StorageUsed       prometheus.Gauge
	FilesStored       prometheus.Gauge
	ChunksStored      prometheus.Gauge
	BlobsStored       prometheus.Gauge

	// Cache metrics
	CacheHits         *prometheus.CounterVec
	CacheMisses       *prometheus.CounterVec
	CacheSize         *prometheus.GaugeVec
	CacheMemoryUsage  *prometheus.GaugeVec

	// Rate limiting metrics
	RateLimitHits     *prometheus.CounterVec
	RateLimitBlocks   *prometheus.CounterVec

	// Admin metrics
	AdminActions      *prometheus.CounterVec
	BannedUsers       prometheus.Gauge
	ContentReports    *prometheus.CounterVec

	// System metrics
	DatabaseQueries   *prometheus.CounterVec
	DatabaseErrors    *prometheus.CounterVec
	GoroutineCount    prometheus.Gauge
	MemoryUsage       prometheus.Gauge

	// Blossom pool metrics
	BlossomPoolServers    *prometheus.GaugeVec
	BlossomPoolRequests   *prometheus.CounterVec
	BlossomPoolErrors     *prometheus.CounterVec
}

// NewMetrics creates and registers all Prometheus metrics
func NewMetrics() *Metrics {
	m := &Metrics{
		// Request metrics
		RequestsTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_requests_total",
				Help: "Total number of HTTP requests",
			},
			[]string{"method", "endpoint", "status_code"},
		),
		RequestDuration: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Name:    "gateway_request_duration_seconds",
				Help:    "HTTP request duration in seconds",
				Buckets: prometheus.DefBuckets,
			},
			[]string{"method", "endpoint"},
		),
		ActiveConnections: prometheus.NewGauge(
			prometheus.GaugeOpts{
				Name: "gateway_active_connections",
				Help: "Number of active HTTP connections",
			},
		),

		// Upload metrics
		UploadsTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_uploads_total",
				Help: "Total number of file uploads",
			},
			[]string{"storage_type", "status"},
		),
		UploadSize: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Name:    "gateway_upload_size_bytes",
				Help:    "Upload file size in bytes",
				Buckets: []float64{1024, 10240, 102400, 1048576, 10485760, 104857600, 1073741824}, // 1KB to 1GB
			},
			[]string{"storage_type"},
		),
		UploadDuration: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Name:    "gateway_upload_duration_seconds",
				Help:    "Upload duration in seconds",
				Buckets: []float64{0.1, 0.5, 1, 5, 10, 30, 60, 300}, // 100ms to 5min
			},
			[]string{"storage_type"},
		),

		// Download metrics
		DownloadsTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_downloads_total",
				Help: "Total number of file downloads",
			},
			[]string{"storage_type", "status"},
		),
		DownloadSize: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Name:    "gateway_download_size_bytes",
				Help:    "Download file size in bytes",
				Buckets: []float64{1024, 10240, 102400, 1048576, 10485760, 104857600, 1073741824},
			},
			[]string{"storage_type"},
		),
		DownloadDuration: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Name:    "gateway_download_duration_seconds",
				Help:    "Download duration in seconds",
				Buckets: []float64{0.1, 0.5, 1, 5, 10, 30, 60, 300},
			},
			[]string{"storage_type"},
		),

		// Stream metrics
		StreamsActive: prometheus.NewGauge(
			prometheus.GaugeOpts{
				Name: "gateway_streams_active",
				Help: "Number of active streams",
			},
		),
		StreamsTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_streams_total",
				Help: "Total number of streams started",
			},
			[]string{"file_type", "status"},
		),
		StreamDuration: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Name:    "gateway_stream_duration_seconds",
				Help:    "Stream duration in seconds",
				Buckets: []float64{1, 10, 60, 300, 1800, 3600}, // 1s to 1h
			},
			[]string{"file_type"},
		),

		// Storage metrics
		StorageUsed: prometheus.NewGauge(
			prometheus.GaugeOpts{
				Name: "gateway_storage_used_bytes",
				Help: "Total storage used in bytes",
			},
		),
		FilesStored: prometheus.NewGauge(
			prometheus.GaugeOpts{
				Name: "gateway_files_stored_total",
				Help: "Total number of files stored",
			},
		),
		ChunksStored: prometheus.NewGauge(
			prometheus.GaugeOpts{
				Name: "gateway_chunks_stored_total",
				Help: "Total number of chunks stored",
			},
		),
		BlobsStored: prometheus.NewGauge(
			prometheus.GaugeOpts{
				Name: "gateway_blobs_stored_total",
				Help: "Total number of blobs stored",
			},
		),

		// Cache metrics
		CacheHits: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_cache_hits_total",
				Help: "Total number of cache hits",
			},
			[]string{"cache_type"},
		),
		CacheMisses: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_cache_misses_total",
				Help: "Total number of cache misses",
			},
			[]string{"cache_type"},
		),
		CacheSize: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Name: "gateway_cache_size_items",
				Help: "Number of items in cache",
			},
			[]string{"cache_type"},
		),
		CacheMemoryUsage: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Name: "gateway_cache_memory_bytes",
				Help: "Memory usage of cache in bytes",
			},
			[]string{"cache_type"},
		),

		// Rate limiting metrics
		RateLimitHits: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_rate_limit_hits_total",
				Help: "Total number of rate limit hits",
			},
			[]string{"limit_type"},
		),
		RateLimitBlocks: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_rate_limit_blocks_total",
				Help: "Total number of rate limit blocks",
			},
			[]string{"limit_type"},
		),

		// Admin metrics
		AdminActions: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_admin_actions_total",
				Help: "Total number of admin actions",
			},
			[]string{"action_type", "admin_pubkey"},
		),
		BannedUsers: prometheus.NewGauge(
			prometheus.GaugeOpts{
				Name: "gateway_banned_users_total",
				Help: "Total number of banned users",
			},
		),
		ContentReports: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_content_reports_total",
				Help: "Total number of content reports",
			},
			[]string{"status"},
		),

		// System metrics
		DatabaseQueries: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_database_queries_total",
				Help: "Total number of database queries",
			},
			[]string{"operation", "table"},
		),
		DatabaseErrors: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_database_errors_total",
				Help: "Total number of database errors",
			},
			[]string{"operation", "table"},
		),
		GoroutineCount: prometheus.NewGauge(
			prometheus.GaugeOpts{
				Name: "gateway_goroutines_active",
				Help: "Number of active goroutines",
			},
		),
		MemoryUsage: prometheus.NewGauge(
			prometheus.GaugeOpts{
				Name: "gateway_memory_usage_bytes",
				Help: "Memory usage in bytes",
			},
		),

		// Blossom pool metrics
		BlossomPoolServers: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Name: "gateway_blossom_pool_servers",
				Help: "Number of Blossom pool servers by status",
			},
			[]string{"status"}, // healthy, unhealthy
		),
		BlossomPoolRequests: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_blossom_pool_requests_total",
				Help: "Total number of Blossom pool requests",
			},
			[]string{"server", "status"},
		),
		BlossomPoolErrors: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "gateway_blossom_pool_errors_total",
				Help: "Total number of Blossom pool errors",
			},
			[]string{"server", "error_type"},
		),
	}

	// Register all metrics
	prometheus.MustRegister(
		m.RequestsTotal,
		m.RequestDuration,
		m.ActiveConnections,
		m.UploadsTotal,
		m.UploadSize,
		m.UploadDuration,
		m.DownloadsTotal,
		m.DownloadSize,
		m.DownloadDuration,
		m.StreamsActive,
		m.StreamsTotal,
		m.StreamDuration,
		m.StorageUsed,
		m.FilesStored,
		m.ChunksStored,
		m.BlobsStored,
		m.CacheHits,
		m.CacheMisses,
		m.CacheSize,
		m.CacheMemoryUsage,
		m.RateLimitHits,
		m.RateLimitBlocks,
		m.AdminActions,
		m.BannedUsers,
		m.ContentReports,
		m.DatabaseQueries,
		m.DatabaseErrors,
		m.GoroutineCount,
		m.MemoryUsage,
		m.BlossomPoolServers,
		m.BlossomPoolRequests,
		m.BlossomPoolErrors,
	)

	return m
}

// HTTPMiddleware wraps HTTP handlers to collect request metrics
func (m *Metrics) HTTPMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		
		// Increment active connections
		m.ActiveConnections.Inc()
		defer m.ActiveConnections.Dec()

		// Wrap response writer to capture status code
		ww := &wrappedWriter{ResponseWriter: w, statusCode: 200}
		
		// Call next handler
		next.ServeHTTP(ww, r)
		
		// Record metrics
		duration := time.Since(start).Seconds()
		endpoint := r.URL.Path
		method := r.Method
		statusCode := ww.statusCode

		m.RequestsTotal.WithLabelValues(method, endpoint, string(rune(statusCode))).Inc()
		m.RequestDuration.WithLabelValues(method, endpoint).Observe(duration)
	})
}

// wrappedWriter wraps http.ResponseWriter to capture status code
type wrappedWriter struct {
	http.ResponseWriter
	statusCode int
}

func (w *wrappedWriter) WriteHeader(statusCode int) {
	w.statusCode = statusCode
	w.ResponseWriter.WriteHeader(statusCode)
}

// RecordUpload records upload metrics
func (m *Metrics) RecordUpload(storageType string, size int64, duration time.Duration, success bool) {
	status := "success"
	if !success {
		status = "error"
	}
	
	m.UploadsTotal.WithLabelValues(storageType, status).Inc()
	m.UploadSize.WithLabelValues(storageType).Observe(float64(size))
	m.UploadDuration.WithLabelValues(storageType).Observe(duration.Seconds())
}

// RecordDownload records download metrics
func (m *Metrics) RecordDownload(storageType string, size int64, duration time.Duration, success bool) {
	status := "success"
	if !success {
		status = "error"
	}
	
	m.DownloadsTotal.WithLabelValues(storageType, status).Inc()
	m.DownloadSize.WithLabelValues(storageType).Observe(float64(size))
	m.DownloadDuration.WithLabelValues(storageType).Observe(duration.Seconds())
}

// RecordStream records streaming metrics
func (m *Metrics) RecordStream(fileType string, duration time.Duration, success bool) {
	status := "success"
	if !success {
		status = "error"
	}
	
	m.StreamsTotal.WithLabelValues(fileType, status).Inc()
	m.StreamDuration.WithLabelValues(fileType).Observe(duration.Seconds())
}

// UpdateStorageMetrics updates storage-related metrics
func (m *Metrics) UpdateStorageMetrics(storageUsed int64, filesCount, chunksCount, blobsCount int) {
	m.StorageUsed.Set(float64(storageUsed))
	m.FilesStored.Set(float64(filesCount))
	m.ChunksStored.Set(float64(chunksCount))
	m.BlobsStored.Set(float64(blobsCount))
}

// RecordCacheOperation records cache hit/miss
func (m *Metrics) RecordCacheOperation(cacheType string, hit bool) {
	if hit {
		m.CacheHits.WithLabelValues(cacheType).Inc()
	} else {
		m.CacheMisses.WithLabelValues(cacheType).Inc()
	}
}

// UpdateCacheMetrics updates cache size and memory usage
func (m *Metrics) UpdateCacheMetrics(cacheType string, size int, memoryUsage int64) {
	m.CacheSize.WithLabelValues(cacheType).Set(float64(size))
	m.CacheMemoryUsage.WithLabelValues(cacheType).Set(float64(memoryUsage))
}

// RecordRateLimit records rate limiting events
func (m *Metrics) RecordRateLimit(limitType string, blocked bool) {
	if blocked {
		m.RateLimitBlocks.WithLabelValues(limitType).Inc()
	} else {
		m.RateLimitHits.WithLabelValues(limitType).Inc()
	}
}

// RecordAdminAction records admin actions
func (m *Metrics) RecordAdminAction(actionType, adminPubkey string) {
	m.AdminActions.WithLabelValues(actionType, adminPubkey[:16]+"...").Inc()
}

// UpdateAdminMetrics updates admin-related metrics
func (m *Metrics) UpdateAdminMetrics(bannedUsersCount int) {
	m.BannedUsers.Set(float64(bannedUsersCount))
}

// RecordContentReport records content reports
func (m *Metrics) RecordContentReport(status string) {
	m.ContentReports.WithLabelValues(status).Inc()
}

// RecordDatabaseOperation records database queries and errors
func (m *Metrics) RecordDatabaseOperation(operation, table string, success bool) {
	m.DatabaseQueries.WithLabelValues(operation, table).Inc()
	if !success {
		m.DatabaseErrors.WithLabelValues(operation, table).Inc()
	}
}

// UpdateSystemMetrics updates system-level metrics
func (m *Metrics) UpdateSystemMetrics(goroutineCount int, memoryUsage int64) {
	m.GoroutineCount.Set(float64(goroutineCount))
	m.MemoryUsage.Set(float64(memoryUsage))
}

// RecordBlossomPoolOperation records Blossom pool metrics
func (m *Metrics) RecordBlossomPoolOperation(server, status string, success bool) {
	m.BlossomPoolRequests.WithLabelValues(server, status).Inc()
	if !success {
		m.BlossomPoolErrors.WithLabelValues(server, "request_failed").Inc()
	}
}

// UpdateBlossomPoolHealth updates Blossom pool server health metrics
func (m *Metrics) UpdateBlossomPoolHealth(healthyCount, unhealthyCount int) {
	m.BlossomPoolServers.WithLabelValues("healthy").Set(float64(healthyCount))
	m.BlossomPoolServers.WithLabelValues("unhealthy").Set(float64(unhealthyCount))
}

// Handler returns the Prometheus metrics HTTP handler
func (m *Metrics) Handler() http.Handler {
	return promhttp.Handler()
}

// StartMetricsServer starts a dedicated metrics server
func (m *Metrics) StartMetricsServer(port int) {
	mux := http.NewServeMux()
	mux.Handle("/metrics", m.Handler())
	
	server := &http.Server{
		Addr:    fmt.Sprintf(":%d", port),
		Handler: mux,
	}
	
	go func() {
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Printf("Metrics server error: %v", err)
		}
	}()
}