package records

import (
	"context"
	"database/sql"

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

const rollupDeltaTableName = "_System_RollupDelta"

var (
	rollupDeltaQueueDepth = promauto.NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "nexuscrm_rollup_delta_queue_depth",
			Help: "Number of rollup delta rows per Status.",
		},
		[]string{"state"},
	)
	rollupDeltaOldestPendingAgeSeconds = promauto.NewGauge(
		prometheus.GaugeOpts{
			Name: "nexuscrm_rollup_delta_oldest_pending_age_seconds",
			Help: "Age in seconds of the oldest dispatchable pending rollup delta row (0 when none pending).",
		},
	)
	rollupDeltaPendingParentLanes = promauto.NewGauge(
		prometheus.GaugeOpts{
			Name: "nexuscrm_rollup_delta_pending_parent_lanes",
			Help: "Number of dispatchable pending rollup parent lanes.",
		},
	)
	rollupDeltaPendingRows = promauto.NewGauge(
		prometheus.GaugeOpts{
			Name: "nexuscrm_rollup_delta_pending_rows",
			Help: "Number of dispatchable pending rollup delta rows.",
		},
	)
	rollupDeltaHotParentLaneDepth = promauto.NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "nexuscrm_rollup_delta_hot_parent_lane_depth",
			Help: "Largest dispatchable pending row counts for the fixed top-N parent lanes. Labels expose rank only, never object or record IDs.",
		},
		[]string{"rank"},
	)
	rollupDeltaRetryRows = promauto.NewGauge(
		prometheus.GaugeOpts{
			Name: "nexuscrm_rollup_delta_retry_rows",
			Help: "Number of non-processed rollup delta rows with RetryCount greater than zero.",
		},
	)
	rollupDeltaMaxRetryCount = promauto.NewGauge(
		prometheus.GaugeOpts{
			Name: "nexuscrm_rollup_delta_max_retry_count",
			Help: "Maximum RetryCount among non-processed rollup delta rows.",
		},
	)
	rollupDeltaParentLockAttempts = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name: "nexuscrm_rollup_delta_parent_lock_attempts_total",
			Help: "Total rollup delta parent-locking update attempts by path and result. No object or record IDs are exposed.",
		},
		[]string{"path", "result"},
	)
	rollupDeltaParentLaneRowsProcessed = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name: "nexuscrm_rollup_delta_parent_lane_rows_processed_total",
			Help: "Total rollup delta rows handled by parent lane, grouped by result only.",
		},
		[]string{"result"},
	)
	rollupDeltaWorkerDrainDurationSeconds = promauto.NewHistogramVec(
		prometheus.HistogramOpts{
			Name:    "nexuscrm_rollup_delta_worker_drain_duration_seconds",
			Help:    "Duration of one rollup delta worker drain tick.",
			Buckets: prometheus.DefBuckets,
		},
		[]string{"result"},
	)
)

var rollupDeltaBacklogStates = []string{
	rollupDeltaStatusPending,
	rollupDeltaStatusProcessed,
	"failed",
}

var rollupDeltaHotParentLaneRanks = []string{"1", "2", "3", "4", "5"}

type rollupDeltaBacklogStats struct {
	depthByState            map[string]int64
	pendingParentLanes      int64
	pendingRows             int64
	hotParentLaneDepthRanks []int64
	oldestPendingAgeSeconds float64
	retryRows               int64
	maxRetryCount           int64
}

func queryRollupDeltaBacklogStats(ctx context.Context, db *sql.DB) (rollupDeltaBacklogStats, error) {
	stats := rollupDeltaBacklogStats{
		depthByState:            map[string]int64{},
		hotParentLaneDepthRanks: make([]int64, 0, len(rollupDeltaHotParentLaneRanks)),
	}
	if db == nil {
		return stats, sql.ErrConnDone
	}

	depthRows, err := db.QueryContext(ctx, "SELECT `Status`, COUNT(*) FROM `"+rollupDeltaTableName+"` GROUP BY `Status`")
	if err != nil {
		return stats, err
	}
	var scanErr error
	func() {
		defer depthRows.Close()
		for depthRows.Next() {
			var status string
			var count int64
			if err := depthRows.Scan(&status, &count); err != nil {
				scanErr = err
				return
			}
			stats.depthByState[status] = count
		}
	}()
	if scanErr != nil {
		return stats, scanErr
	}
	if err := depthRows.Err(); err != nil {
		return stats, err
	}

	err = db.QueryRowContext(ctx,
		"SELECT COUNT(*), COUNT(DISTINCT `ParentObject`, `ParentRecordId`) FROM `"+rollupDeltaTableName+"` "+
			"WHERE `Status` = ? AND (`NextAttemptAt` IS NULL OR `NextAttemptAt` <= NOW())",
		rollupDeltaStatusPending,
	).Scan(&stats.pendingRows, &stats.pendingParentLanes)
	if err != nil {
		return stats, err
	}

	hotRows, err := db.QueryContext(ctx,
		"SELECT COUNT(*) AS `LaneDepth` FROM `"+rollupDeltaTableName+"` "+
			"WHERE `Status` = ? AND (`NextAttemptAt` IS NULL OR `NextAttemptAt` <= NOW()) "+
			"GROUP BY `ParentObject`, `ParentRecordId` ORDER BY `LaneDepth` DESC LIMIT ?",
		rollupDeltaStatusPending, len(rollupDeltaHotParentLaneRanks),
	)
	if err != nil {
		return stats, err
	}
	scanErr = nil
	func() {
		defer hotRows.Close()
		for hotRows.Next() {
			var laneDepth sql.NullInt64
			if err := hotRows.Scan(&laneDepth); err != nil {
				scanErr = err
				return
			}
			if laneDepth.Valid {
				stats.hotParentLaneDepthRanks = append(stats.hotParentLaneDepthRanks, laneDepth.Int64)
			}
		}
	}()
	if scanErr != nil {
		return stats, scanErr
	}
	if err := hotRows.Err(); err != nil {
		return stats, err
	}

	var oldestPendingAge sql.NullFloat64
	err = db.QueryRowContext(ctx,
		"SELECT TIMESTAMPDIFF(SECOND, MIN(`CreatedDate`), NOW()) FROM `"+rollupDeltaTableName+"` "+
			"WHERE `Status` = ? AND (`NextAttemptAt` IS NULL OR `NextAttemptAt` <= NOW())",
		rollupDeltaStatusPending,
	).Scan(&oldestPendingAge)
	if err != nil {
		return stats, err
	}
	if oldestPendingAge.Valid && oldestPendingAge.Float64 > 0 {
		stats.oldestPendingAgeSeconds = oldestPendingAge.Float64
	}

	var retryRows, maxRetry sql.NullInt64
	err = db.QueryRowContext(ctx,
		"SELECT COUNT(*), COALESCE(MAX(`RetryCount`), 0) FROM `"+rollupDeltaTableName+"` "+
			"WHERE `Status` <> ? AND `RetryCount` > 0",
		rollupDeltaStatusProcessed,
	).Scan(&retryRows, &maxRetry)
	if err != nil {
		return stats, err
	}
	if retryRows.Valid {
		stats.retryRows = retryRows.Int64
	}
	if maxRetry.Valid {
		stats.maxRetryCount = maxRetry.Int64
	}

	return stats, nil
}

func applyRollupDeltaBacklogStats(stats rollupDeltaBacklogStats) {
	for _, state := range rollupDeltaBacklogStates {
		rollupDeltaQueueDepth.WithLabelValues(state).Set(float64(stats.depthByState[state]))
	}
	rollupDeltaOldestPendingAgeSeconds.Set(stats.oldestPendingAgeSeconds)
	rollupDeltaRetryRows.Set(float64(stats.retryRows))
	rollupDeltaMaxRetryCount.Set(float64(stats.maxRetryCount))
	rollupDeltaHotParentLaneDepth.Reset()
	rollupDeltaPendingParentLanes.Set(float64(stats.pendingParentLanes))
	rollupDeltaPendingRows.Set(float64(stats.pendingRows))
	for i, rank := range rollupDeltaHotParentLaneRanks {
		var depth int64
		if i < len(stats.hotParentLaneDepthRanks) {
			depth = stats.hotParentLaneDepthRanks[i]
		}
		rollupDeltaHotParentLaneDepth.WithLabelValues(rank).Set(float64(depth))
	}
}

// InitializeRollupDeltaBacklogMetrics creates every fixed-label time series
// without reading the database. The API observer's stale metric distinguishes
// these initial zero values from a successful exact snapshot.
func InitializeRollupDeltaBacklogMetrics() {
	applyRollupDeltaBacklogStats(rollupDeltaBacklogStats{depthByState: map[string]int64{}})
}

// RefreshRollupDeltaBacklogStats performs one exact, read-only backlog
// observation. It publishes only a complete successful snapshot, so callers
// can retain the last-good Prometheus values when any query fails.
func RefreshRollupDeltaBacklogStats(ctx context.Context, db *sql.DB) error {
	stats, err := queryRollupDeltaBacklogStats(ctx, db)
	if err != nil {
		return err
	}
	applyRollupDeltaBacklogStats(stats)
	return nil
}
