package backbeat

import (
	"time"

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

// Metrics provides comprehensive observability for BACKBEAT pulse service
// Supports BACKBEAT-PER-001, BACKBEAT-PER-002, BACKBEAT-PER-003 monitoring
type Metrics struct {
	// BACKBEAT-PER-001: End-to-end delivery p95 ≤ 100ms at 2Hz
	BeatPublishDuration prometheus.Histogram
	BeatDeliveryLatency prometheus.Histogram

	// BACKBEAT-PER-002: Pulse jitter p95 ≤ 20ms
	PulseJitter prometheus.Histogram
	BeatTiming  prometheus.Histogram

	// BACKBEAT-PER-003: SDK timer drift ≤ 1% over 1 hour
	TimerDrift prometheus.Gauge
	HLCDrift   prometheus.Gauge

	// Leadership and cluster health
	IsLeader          prometheus.Gauge
	LeadershipChanges prometheus.Counter
	ClusterSize       prometheus.Gauge

	// Tempo and beat metrics
	CurrentTempo     prometheus.Gauge
	BeatCounter      prometheus.Counter
	DownbeatCounter  prometheus.Counter
	PhaseTransitions prometheus.CounterVec

	// Error and degradation metrics
	TempoChangeErrors  prometheus.Counter
	LeadershipLoss     prometheus.Counter
	DegradationMode    prometheus.Gauge
	NATSConnectionLoss prometheus.Counter

	// Performance metrics
	BeatFrameSize     prometheus.Histogram
	NATSPublishErrors prometheus.Counter

	// BACKBEAT-OBS-002: Reverb aggregation metrics
	ReverbAgentsReporting      prometheus.Gauge
	ReverbOnTimeReviews        prometheus.Gauge
	ReverbTempoDriftMS         prometheus.Gauge
	ReverbWindowsCompleted     prometheus.Counter
	ReverbClaimsProcessed      prometheus.Counter
	ReverbWindowProcessingTime prometheus.Histogram
	ReverbBarReportSize        prometheus.Histogram
	ReverbWindowsActive        prometheus.Gauge
	ReverbClaimsPerWindow      prometheus.Histogram
}

// NewMetrics creates and registers all BACKBEAT metrics
func NewMetrics() *Metrics {
	return &Metrics{
		// BACKBEAT-PER-001: End-to-end delivery monitoring
		BeatPublishDuration: promauto.NewHistogram(prometheus.HistogramOpts{
			Name:      "backbeat_beat_publish_duration_seconds",
			Help:      "Time spent publishing beat frames to NATS",
			Namespace: "backbeat",
			Subsystem: "pulse",
			Buckets:   prometheus.ExponentialBuckets(0.001, 2, 10), // 1ms to 1s
		}),

		BeatDeliveryLatency: promauto.NewHistogram(prometheus.HistogramOpts{
			Name:      "backbeat_beat_delivery_latency_seconds",
			Help:      "End-to-end beat delivery latency (BACKBEAT-PER-001: p95 ≤ 100ms)",
			Namespace: "backbeat",
			Subsystem: "pulse",
			Buckets:   prometheus.ExponentialBuckets(0.001, 2, 10),
		}),

		// BACKBEAT-PER-002: Pulse jitter monitoring
		PulseJitter: promauto.NewHistogram(prometheus.HistogramOpts{
			Name:      "backbeat_pulse_jitter_seconds",
			Help:      "Beat timing jitter (BACKBEAT-PER-002: p95 ≤ 20ms)",
			Namespace: "backbeat",
			Subsystem: "pulse",
			Buckets:   []float64{0.001, 0.005, 0.010, 0.015, 0.020, 0.025, 0.050, 0.100},
		}),

		BeatTiming: promauto.NewHistogram(prometheus.HistogramOpts{
			Name:      "backbeat_beat_timing_accuracy_seconds",
			Help:      "Accuracy of beat timing relative to expected schedule",
			Namespace: "backbeat",
			Subsystem: "pulse",
			Buckets:   prometheus.ExponentialBuckets(0.0001, 2, 12),
		}),

		// BACKBEAT-PER-003: Timer drift monitoring
		TimerDrift: promauto.NewGauge(prometheus.GaugeOpts{
			Name:      "backbeat_timer_drift_ratio",
			Help:      "Timer drift ratio (BACKBEAT-PER-003: ≤ 1% over 1 hour)",
			Namespace: "backbeat",
			Subsystem: "pulse",
		}),

		HLCDrift: promauto.NewGauge(prometheus.GaugeOpts{
			Name:      "backbeat_hlc_drift_seconds",
			Help:      "HLC drift from last leader sync",
			Namespace: "backbeat",
			Subsystem: "pulse",
		}),

		// Leadership metrics
		IsLeader: promauto.NewGauge(prometheus.GaugeOpts{
			Name:      "backbeat_is_leader",
			Help:      "1 if this node is the current leader, 0 otherwise",
			Namespace: "backbeat",
			Subsystem: "cluster",
		}),

		LeadershipChanges: promauto.NewCounter(prometheus.CounterOpts{
			Name:      "backbeat_leadership_changes_total",
			Help:      "Total number of leadership changes",
			Namespace: "backbeat",
			Subsystem: "cluster",
		}),

		ClusterSize: promauto.NewGauge(prometheus.GaugeOpts{
			Name:      "backbeat_cluster_size",
			Help:      "Number of nodes in the cluster",
			Namespace: "backbeat",
			Subsystem: "cluster",
		}),

		// Tempo and beat metrics
		CurrentTempo: promauto.NewGauge(prometheus.GaugeOpts{
			Name:      "backbeat_current_tempo_bpm",
			Help:      "Current tempo in beats per minute",
			Namespace: "backbeat",
			Subsystem: "pulse",
		}),

		BeatCounter: promauto.NewCounter(prometheus.CounterOpts{
			Name:      "backbeat_beats_total",
			Help:      "Total number of beats published",
			Namespace: "backbeat",
			Subsystem: "pulse",
		}),

		DownbeatCounter: promauto.NewCounter(prometheus.CounterOpts{
			Name:      "backbeat_downbeats_total",
			Help:      "Total number of downbeats published",
			Namespace: "backbeat",
			Subsystem: "pulse",
		}),

		PhaseTransitions: *promauto.NewCounterVec(prometheus.CounterOpts{
			Name:      "backbeat_phase_transitions_total",
			Help:      "Total number of phase transitions by phase name",
			Namespace: "backbeat",
			Subsystem: "pulse",
		}, []string{"phase", "from_phase"}),

		// Error metrics
		TempoChangeErrors: promauto.NewCounter(prometheus.CounterOpts{
			Name:      "backbeat_tempo_change_errors_total",
			Help:      "Total number of rejected tempo change requests",
			Namespace: "backbeat",
			Subsystem: "control",
		}),

		LeadershipLoss: promauto.NewCounter(prometheus.CounterOpts{
			Name:      "backbeat_leadership_loss_total",
			Help:      "Total number of times this node lost leadership",
			Namespace: "backbeat",
			Subsystem: "cluster",
		}),

		DegradationMode: promauto.NewGauge(prometheus.GaugeOpts{
			Name:      "backbeat_degradation_mode",
			Help:      "1 if running in degradation mode (BACKBEAT-REQ-003), 0 otherwise",
			Namespace: "backbeat",
			Subsystem: "pulse",
		}),

		NATSConnectionLoss: promauto.NewCounter(prometheus.CounterOpts{
			Name:      "backbeat_nats_connection_loss_total",
			Help:      "Total number of NATS connection losses",
			Namespace: "backbeat",
			Subsystem: "transport",
		}),

		// Performance metrics
		BeatFrameSize: promauto.NewHistogram(prometheus.HistogramOpts{
			Name:      "backbeat_beat_frame_size_bytes",
			Help:      "Size of serialized beat frames",
			Namespace: "backbeat",
			Subsystem: "pulse",
			Buckets:   prometheus.ExponentialBuckets(100, 2, 10),
		}),

		NATSPublishErrors: promauto.NewCounter(prometheus.CounterOpts{
			Name:      "backbeat_nats_publish_errors_total",
			Help:      "Total number of NATS publish errors",
			Namespace: "backbeat",
			Subsystem: "transport",
		}),

		// BACKBEAT-OBS-002: Reverb aggregation metrics
		ReverbAgentsReporting: promauto.NewGauge(prometheus.GaugeOpts{
			Name:      "backbeat_reverb_agents_reporting",
			Help:      "Number of agents reporting in current window (BACKBEAT-OBS-002)",
			Namespace: "backbeat",
			Subsystem: "reverb",
		}),

		ReverbOnTimeReviews: promauto.NewGauge(prometheus.GaugeOpts{
			Name:      "backbeat_reverb_on_time_reviews",
			Help:      "Number of on-time reviews completed (BACKBEAT-OBS-002)",
			Namespace: "backbeat",
			Subsystem: "reverb",
		}),

		ReverbTempoDriftMS: promauto.NewGauge(prometheus.GaugeOpts{
			Name:      "backbeat_reverb_tempo_drift_ms",
			Help:      "Current tempo drift in milliseconds (BACKBEAT-OBS-002)",
			Namespace: "backbeat",
			Subsystem: "reverb",
		}),

		ReverbWindowsCompleted: promauto.NewCounter(prometheus.CounterOpts{
			Name:      "backbeat_reverb_windows_completed_total",
			Help:      "Total number of windows completed (BACKBEAT-OBS-002)",
			Namespace: "backbeat",
			Subsystem: "reverb",
		}),

		ReverbClaimsProcessed: promauto.NewCounter(prometheus.CounterOpts{
			Name:      "backbeat_reverb_claims_processed_total",
			Help:      "Total number of status claims processed (BACKBEAT-OBS-002)",
			Namespace: "backbeat",
			Subsystem: "reverb",
		}),

		ReverbWindowProcessingTime: promauto.NewHistogram(prometheus.HistogramOpts{
			Name:      "backbeat_reverb_window_processing_seconds",
			Help:      "Time to process and emit a window report (BACKBEAT-PER-002: ≤ 1 beat)",
			Namespace: "backbeat",
			Subsystem: "reverb",
			Buckets:   prometheus.ExponentialBuckets(0.001, 2, 12), // 1ms to 4s
		}),

		ReverbBarReportSize: promauto.NewHistogram(prometheus.HistogramOpts{
			Name:      "backbeat_reverb_bar_report_size_bytes",
			Help:      "Size of serialized bar reports",
			Namespace: "backbeat",
			Subsystem: "reverb",
			Buckets:   prometheus.ExponentialBuckets(100, 2, 10),
		}),

		ReverbWindowsActive: promauto.NewGauge(prometheus.GaugeOpts{
			Name:      "backbeat_reverb_windows_active",
			Help:      "Number of active windows being aggregated",
			Namespace: "backbeat",
			Subsystem: "reverb",
		}),

		ReverbClaimsPerWindow: promauto.NewHistogram(prometheus.HistogramOpts{
			Name:      "backbeat_reverb_claims_per_window",
			Help:      "Number of claims processed per window",
			Namespace: "backbeat",
			Subsystem: "reverb",
			Buckets:   prometheus.ExponentialBuckets(1, 2, 15), // 1 to 32k claims
		}),
	}
}

// RecordBeatPublish records metrics for a published beat
func (m *Metrics) RecordBeatPublish(duration time.Duration, frameSize int, isDownbeat bool, phase string) {
	m.BeatPublishDuration.Observe(duration.Seconds())
	m.BeatFrameSize.Observe(float64(frameSize))
	m.BeatCounter.Inc()

	if isDownbeat {
		m.DownbeatCounter.Inc()
	}
}

// RecordPulseJitter records beat timing jitter
func (m *Metrics) RecordPulseJitter(jitter time.Duration) {
	m.PulseJitter.Observe(jitter.Seconds())
}

// RecordBeatTiming records beat timing accuracy
func (m *Metrics) RecordBeatTiming(expectedTime, actualTime time.Time) {
	diff := actualTime.Sub(expectedTime).Abs()
	m.BeatTiming.Observe(diff.Seconds())
}

// UpdateTempoMetrics updates tempo-related metrics
func (m *Metrics) UpdateTempoMetrics(currentBPM int) {
	m.CurrentTempo.Set(float64(currentBPM))
}

// UpdateLeadershipMetrics updates leadership-related metrics
func (m *Metrics) UpdateLeadershipMetrics(isLeader bool, clusterSize int) {
	if isLeader {
		m.IsLeader.Set(1)
	} else {
		m.IsLeader.Set(0)
	}
	m.ClusterSize.Set(float64(clusterSize))
}

// RecordLeadershipChange records a leadership change event
func (m *Metrics) RecordLeadershipChange(becameLeader bool) {
	m.LeadershipChanges.Inc()
	if !becameLeader {
		m.LeadershipLoss.Inc()
	}
}

// UpdateDriftMetrics updates drift-related metrics for BACKBEAT-PER-003
func (m *Metrics) UpdateDriftMetrics(timerDriftRatio float64, hlcDriftSeconds float64) {
	m.TimerDrift.Set(timerDriftRatio)
	m.HLCDrift.Set(hlcDriftSeconds)
}

// UpdateDegradationMode updates degradation mode status
func (m *Metrics) UpdateDegradationMode(inDegradationMode bool) {
	if inDegradationMode {
		m.DegradationMode.Set(1)
	} else {
		m.DegradationMode.Set(0)
	}
}

// RecordTempoChangeError records a tempo change error
func (m *Metrics) RecordTempoChangeError() {
	m.TempoChangeErrors.Inc()
}

// RecordNATSError records NATS-related errors
func (m *Metrics) RecordNATSError(errorType string) {
	switch errorType {
	case "connection_loss":
		m.NATSConnectionLoss.Inc()
	case "publish_error":
		m.NATSPublishErrors.Inc()
	}
}

// RecordPhaseTransition records a phase transition
func (m *Metrics) RecordPhaseTransition(fromPhase, toPhase string) {
	m.PhaseTransitions.WithLabelValues(toPhase, fromPhase).Inc()
}

// RecordReverbWindow records metrics for a completed reverb window
func (m *Metrics) RecordReverbWindow(processingTime time.Duration, claimsCount int, agentsReporting int, onTimeReviews int, tempoDriftMS int, reportSize int) {
	m.ReverbWindowsCompleted.Inc()
	m.ReverbWindowProcessingTime.Observe(processingTime.Seconds())
	m.ReverbClaimsPerWindow.Observe(float64(claimsCount))
	m.ReverbBarReportSize.Observe(float64(reportSize))

	// Update current window metrics
	m.ReverbAgentsReporting.Set(float64(agentsReporting))
	m.ReverbOnTimeReviews.Set(float64(onTimeReviews))
	m.ReverbTempoDriftMS.Set(float64(tempoDriftMS))
}

// RecordReverbClaim records a processed status claim
func (m *Metrics) RecordReverbClaim() {
	m.ReverbClaimsProcessed.Inc()
}

// UpdateReverbActiveWindows updates the number of active windows being tracked
func (m *Metrics) UpdateReverbActiveWindows(count int) {
	m.ReverbWindowsActive.Set(float64(count))
}
