package main

import (
	"sort"
	"sync"
	"time"
)

// Metrics collects performance data during load tests.
type Metrics struct {
	mu sync.Mutex

	// Write metrics
	writeLatencies []time.Duration
	writeErrors    int
	writeCount     int

	// Read metrics
	readLatencies []time.Duration
	readErrors    int
	readCount     int

	// Timing
	startTime time.Time
	endTime   time.Time

	// Degradation tracking
	baselineP99     time.Duration
	loadedP99       time.Duration
	degradationPct  float64
	readerCount     int
	backgroundWrite int
}

// NewMetrics creates a new Metrics collector.
func NewMetrics() *Metrics {
	return &Metrics{
		startTime: time.Now(),
	}
}

// RecordWrite records a write operation latency and error status.
func (m *Metrics) RecordWrite(latency time.Duration, err error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.writeLatencies = append(m.writeLatencies, latency)
	m.writeCount++
	if err != nil {
		m.writeErrors++
	}
}

// RecordRead records a read operation latency and error status.
func (m *Metrics) RecordRead(latency time.Duration, err error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.readLatencies = append(m.readLatencies, latency)
	m.readCount++
	if err != nil {
		m.readErrors++
	}
}

// Stop marks the end of the test.
func (m *Metrics) Stop() {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.endTime = time.Now()
}

// Duration returns the test duration.
func (m *Metrics) Duration() time.Duration {
	m.mu.Lock()
	defer m.mu.Unlock()

	end := m.endTime
	if end.IsZero() {
		end = time.Now()
	}
	return end.Sub(m.startTime)
}

// WriteCount returns the total number of write operations.
func (m *Metrics) WriteCount() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.writeCount
}

// WriteErrors returns the number of failed write operations.
func (m *Metrics) WriteErrors() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.writeErrors
}

// WriteErrorRate returns the write error rate as a percentage.
func (m *Metrics) WriteErrorRate() float64 {
	m.mu.Lock()
	defer m.mu.Unlock()

	if m.writeCount == 0 {
		return 0
	}
	return float64(m.writeErrors) / float64(m.writeCount) * 100
}

// WriteThroughput returns the average writes per second.
func (m *Metrics) WriteThroughput() float64 {
	m.mu.Lock()
	defer m.mu.Unlock()

	var duration time.Duration
	if m.endTime.IsZero() {
		duration = time.Since(m.startTime)
	} else {
		duration = m.endTime.Sub(m.startTime)
	}
	if duration <= 0 {
		return 0
	}
	return float64(m.writeCount) / duration.Seconds()
}

// WriteP50 returns the 50th percentile write latency.
func (m *Metrics) WriteP50() time.Duration {
	return m.writePercentile(0.50)
}

// WriteP99 returns the 99th percentile write latency.
func (m *Metrics) WriteP99() time.Duration {
	return m.writePercentile(0.99)
}

// WriteMax returns the maximum write latency.
func (m *Metrics) WriteMax() time.Duration {
	m.mu.Lock()
	defer m.mu.Unlock()

	if len(m.writeLatencies) == 0 {
		return 0
	}

	sorted := make([]time.Duration, len(m.writeLatencies))
	copy(sorted, m.writeLatencies)
	sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })

	return sorted[len(sorted)-1]
}

func (m *Metrics) writePercentile(p float64) time.Duration {
	m.mu.Lock()
	defer m.mu.Unlock()

	if len(m.writeLatencies) == 0 {
		return 0
	}

	sorted := make([]time.Duration, len(m.writeLatencies))
	copy(sorted, m.writeLatencies)
	sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })

	idx := int(float64(len(sorted)-1) * p)
	return sorted[idx]
}

// ReadCount returns the total number of read operations.
func (m *Metrics) ReadCount() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.readCount
}

// ReadErrors returns the number of failed read operations.
func (m *Metrics) ReadErrors() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.readErrors
}

// ReadP50 returns the 50th percentile read latency.
func (m *Metrics) ReadP50() time.Duration {
	return m.readPercentile(0.50)
}

// ReadP99 returns the 99th percentile read latency.
func (m *Metrics) ReadP99() time.Duration {
	return m.readPercentile(0.99)
}

func (m *Metrics) readPercentile(p float64) time.Duration {
	m.mu.Lock()
	defer m.mu.Unlock()

	if len(m.readLatencies) == 0 {
		return 0
	}

	sorted := make([]time.Duration, len(m.readLatencies))
	copy(sorted, m.readLatencies)
	sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })

	idx := int(float64(len(sorted)-1) * p)
	return sorted[idx]
}

// SetBaselineP99 records the baseline read P99 latency (no concurrent load).
func (m *Metrics) SetBaselineP99(latency time.Duration) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.baselineP99 = latency
}

// SetLoadedP99 records the P99 read latency under concurrent load.
func (m *Metrics) SetLoadedP99(latency time.Duration) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.loadedP99 = latency
}

// SetDegradation records the degradation percentage and test parameters.
func (m *Metrics) SetDegradation(degradationPct float64, readers, backgroundWriteRPS int) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.degradationPct = degradationPct
	m.readerCount = readers
	m.backgroundWrite = backgroundWriteRPS
}

// BaselineP99 returns the baseline P99 latency.
func (m *Metrics) BaselineP99() time.Duration {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.baselineP99
}

// LoadedP99 returns the P99 latency under load.
func (m *Metrics) LoadedP99() time.Duration {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.loadedP99
}

// DegradationPct returns the latency degradation percentage.
func (m *Metrics) DegradationPct() float64 {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.degradationPct
}

// ReaderCount returns the number of concurrent readers tested.
func (m *Metrics) ReaderCount() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.readerCount
}
