package rest

import (
	"context"
	"database/sql"
	"net/http"
	"runtime"
	"time"

	"github.com/gin-gonic/gin"

	fileops "github.com/nexuscrm/backend/internal/features/files"
	"github.com/nexuscrm/backend/internal/interfaces/middleware"
)

type SentryMetricsHandler struct {
	db        *sql.DB
	version   string
	startedAt time.Time
}

func NewSentryMetricsHandler(db *sql.DB, version string) *SentryMetricsHandler {
	return &SentryMetricsHandler{
		db:        db,
		version:   version,
		startedAt: time.Now(),
	}
}

// Get handles GET /internal/sentry-metrics.
//
// Returns a JSON snapshot of process-internal state: Go runtime,
// DB pool stats, HTTP error counters, outbox queue counts, and file-upload
// operation hygiene counters.
// Sentry polls this every 10 minutes. See
// docs/design/sentry-ops-monitoring.md §3.2.
func (h *SentryMetricsHandler) Get(c *gin.Context) {
	var mem runtime.MemStats
	runtime.ReadMemStats(&mem)

	var pool sql.DBStats
	if h.db != nil {
		pool = h.db.Stats()
	}

	now := time.Now().UTC()
	outbox := h.queryOutboxCounts(c.Request.Context())
	fileUploadOps, _ := fileops.QueryUploadOperationMetrics(c.Request.Context(), h.db, now)

	resp := gin.H{
		"collected_at": now.Format(time.RFC3339),
		"version":      h.version,
		"uptime_sec":   int64(time.Since(h.startedAt).Seconds()),
		"runtime": gin.H{
			"goroutines":      runtime.NumGoroutine(),
			"heap_alloc_mb":   mem.HeapAlloc / 1024 / 1024,
			"heap_inuse_mb":   mem.HeapInuse / 1024 / 1024,
			"gc_pause_p99_ms": lastGCPauseMS(&mem),
			"num_gc":          mem.NumGC,
		},
		"db_pool": gin.H{
			"open_conns":       pool.OpenConnections,
			"in_use":           pool.InUse,
			"idle":             pool.Idle,
			"wait_count":       pool.WaitCount,
			"wait_duration_ms": pool.WaitDuration.Milliseconds(),
		},
		"http": gin.H{
			"requests_total":  middleware.GetHTTPRequestsTotalCount(),
			"error_5xx_count": middleware.GetHTTP5xxCount(),
			"panic_count":     middleware.GetPanicRecoveredCount(),
		},
		"outbox":                 outbox,
		"file_upload_operations": fileUploadOps,
	}
	c.JSON(http.StatusOK, resp)
}

// lastGCPauseMS returns the most recent GC pause in milliseconds.
func lastGCPauseMS(m *runtime.MemStats) uint64 {
	if m.NumGC == 0 {
		return 0
	}
	idx := (m.NumGC + 255) % 256
	return m.PauseNs[idx] / 1_000_000
}

// queryOutboxCounts returns pending/processing/processed/failed counts
// grouped by Status.
// Uses a 2-second query timeout; any error yields zero counts — the endpoint
// must never fail because of an outbox query error, since that would prevent
// sentry from learning about other health signals.
//
// The 'processing' bucket is surfaced so a sustained non-zero value makes
// stuck-in-processing rows visible to the existing dashboard: the
// stale-processing reclaim predicate (see outbox_repository.go) normally
// clears these within 5 minutes, so a persistent non-zero here flags a
// dispatch regression.
func (h *SentryMetricsHandler) queryOutboxCounts(ctx context.Context) gin.H {
	result := gin.H{"pending": int64(0), "processing": int64(0), "processed": int64(0), "failed": int64(0)}
	if h.db == nil {
		return result
	}
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()
	rows, err := h.db.QueryContext(ctx, "SELECT `Status`, COUNT(*) FROM `_System_OutboxEvent` GROUP BY `Status`")
	if err != nil {
		return result
	}
	defer rows.Close()
	for rows.Next() {
		var status string
		var count int64
		if err := rows.Scan(&status, &count); err != nil {
			continue
		}
		switch status {
		case "pending":
			result["pending"] = count
		case "processing":
			result["processing"] = count
		case "processed":
			result["processed"] = count
		case "failed":
			result["failed"] = count
		}
	}
	return result
}
