package catchup

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestNewPeerMetrics(t *testing.T) {
	metrics := NewPeerMetrics()

	assert.NotNil(t, metrics)
	assert.Equal(t, float64(1.0), metrics.TrustScore)
	assert.Equal(t, time.Hour, metrics.FastestResponse)
	assert.Equal(t, StateClosed, metrics.CircuitBreakerState)
	assert.Equal(t, int64(0), metrics.SuccessfulRequests)
	assert.Equal(t, int64(0), metrics.FailedRequests)
	assert.Equal(t, int64(0), metrics.TotalRequests)
}

func TestPeerMetrics_RecordSuccess(t *testing.T) {
	metrics := NewPeerMetrics()
	responseTime := 100 * time.Millisecond
	bytesReceived := int64(1024)
	headersReceived := 5

	metrics.RecordSuccess(responseTime, bytesReceived, headersReceived)

	assert.Equal(t, int64(1), metrics.SuccessfulRequests)
	assert.Equal(t, int64(1), metrics.TotalRequests)
	assert.Equal(t, 1, metrics.ConsecutiveSuccesses)
	assert.Equal(t, 0, metrics.ConsecutiveFailures)
	assert.Equal(t, bytesReceived, metrics.BytesReceived)
	assert.Equal(t, int64(headersReceived), metrics.HeadersReceived)
	assert.Equal(t, responseTime, metrics.LastResponseTime)
	assert.Equal(t, responseTime, metrics.FastestResponse)
	assert.Equal(t, responseTime, metrics.SlowestResponse)
	assert.Equal(t, responseTime, metrics.AverageResponseTime)
	assert.True(t, metrics.TrustScore >= 1.0) // Trust score improved or stayed same
	assert.True(t, time.Since(metrics.LastSuccessTime) < time.Second)
}

func TestPeerMetrics_RecordSuccess_MultipleRequests(t *testing.T) {
	metrics := NewPeerMetrics()

	// First request
	metrics.RecordSuccess(100*time.Millisecond, 1024, 5)
	firstAvg := metrics.AverageResponseTime

	// Second request with different response time
	metrics.RecordSuccess(200*time.Millisecond, 2048, 3)

	assert.Equal(t, int64(2), metrics.SuccessfulRequests)
	assert.Equal(t, int64(3072), metrics.BytesReceived) // 1024 + 2048
	assert.Equal(t, int64(8), metrics.HeadersReceived)  // 5 + 3
	assert.Equal(t, 100*time.Millisecond, metrics.FastestResponse)
	assert.Equal(t, 200*time.Millisecond, metrics.SlowestResponse)
	assert.NotEqual(t, firstAvg, metrics.AverageResponseTime) // Average should be updated
}

func TestPeerMetrics_RecordFailure(t *testing.T) {
	metrics := NewPeerMetrics()
	initialTrustScore := metrics.TrustScore

	metrics.RecordFailure()

	assert.Equal(t, int64(1), metrics.FailedRequests)
	assert.Equal(t, int64(1), metrics.TotalRequests)
	assert.Equal(t, 1, metrics.ConsecutiveFailures)
	assert.Equal(t, 0, metrics.ConsecutiveSuccesses)
	assert.True(t, metrics.TrustScore < initialTrustScore) // Trust score decreased
	assert.True(t, time.Since(metrics.LastFailureTime) < time.Second)
}

func TestPeerMetrics_RecordMaliciousActivity(t *testing.T) {
	metrics := NewPeerMetrics()
	initialTrustScore := metrics.TrustScore

	metrics.RecordMaliciousActivity()

	assert.Equal(t, int64(1), metrics.MaliciousAttempts)
	assert.True(t, metrics.TrustScore < initialTrustScore*0.6) // Significant trust penalty
}

func TestPeerMetrics_RecordInvalidData(t *testing.T) {
	metrics := NewPeerMetrics()
	initialTrustScore := metrics.TrustScore

	metrics.RecordInvalidData()

	assert.Equal(t, int64(1), metrics.InvalidDataReceived)
	assert.True(t, metrics.TrustScore < initialTrustScore*0.9) // Trust penalty
}

func TestPeerMetrics_GetSuccessRate(t *testing.T) {
	metrics := NewPeerMetrics()

	// No requests yet
	assert.Equal(t, float64(0), metrics.GetSuccessRate())

	// Add some successful and failed requests
	metrics.RecordSuccess(100*time.Millisecond, 1024, 1)
	metrics.RecordSuccess(100*time.Millisecond, 1024, 1)
	metrics.RecordFailure()

	expectedRate := float64(2) / float64(3) * 100 // 66.67%
	assert.InDelta(t, expectedRate, metrics.GetSuccessRate(), 0.01)
}

func TestPeerMetrics_IsTrusted(t *testing.T) {
	tests := []struct {
		name     string
		setup    func(*PeerMetrics)
		expected bool
	}{
		{
			name: "new peer should be trusted",
			setup: func(pm *PeerMetrics) {
				pm.RecordSuccess(100*time.Millisecond, 1024, 1)
			},
			expected: true,
		},
		{
			name: "peer with low trust score should not be trusted",
			setup: func(pm *PeerMetrics) {
				pm.TrustScore = 0.3
				pm.RecordSuccess(100*time.Millisecond, 1024, 1)
			},
			expected: false,
		},
		{
			name: "peer with no successful requests should not be trusted",
			setup: func(pm *PeerMetrics) {
				pm.RecordFailure()
			},
			expected: false,
		},
		{
			name: "peer with too many consecutive failures should not be trusted",
			setup: func(pm *PeerMetrics) {
				pm.RecordSuccess(100*time.Millisecond, 1024, 1)
				for range 6 {
					pm.RecordFailure()
				}
			},
			expected: false,
		},
		{
			name: "peer with malicious attempts should not be trusted",
			setup: func(pm *PeerMetrics) {
				pm.RecordSuccess(100*time.Millisecond, 1024, 1)
				pm.RecordMaliciousActivity()
			},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			metrics := NewPeerMetrics()
			tt.setup(metrics)
			assert.Equal(t, tt.expected, metrics.IsTrusted())
		})
	}
}

func TestPeerMetrics_IsHealthy(t *testing.T) {
	tests := []struct {
		name     string
		setup    func(*PeerMetrics)
		expected bool
	}{
		{
			name: "new peer with recent success should be healthy",
			setup: func(pm *PeerMetrics) {
				pm.RecordSuccess(100*time.Millisecond, 1024, 1)
			},
			expected: true,
		},
		{
			name: "peer with recent failure should not be healthy",
			setup: func(pm *PeerMetrics) {
				pm.RecordFailure()
			},
			expected: false,
		},
		{
			name: "peer with circuit breaker open should not be healthy",
			setup: func(pm *PeerMetrics) {
				pm.RecordSuccess(100*time.Millisecond, 1024, 1)
				pm.CircuitBreakerState = StateOpen
			},
			expected: false,
		},
		{
			name: "peer with good success rate but no recent activity should be healthy",
			setup: func(pm *PeerMetrics) {
				// Simulate good historical performance
				for range 10 {
					pm.RecordSuccess(100*time.Millisecond, 1024, 1)
				}
				// But make last success old
				pm.LastSuccessTime = time.Now().Add(-10 * time.Minute)
			},
			expected: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			metrics := NewPeerMetrics()
			tt.setup(metrics)
			assert.Equal(t, tt.expected, metrics.IsHealthy())
		})
	}
}

func TestPeerMetrics_GetStats(t *testing.T) {
	metrics := NewPeerMetrics()
	responseTime := 100 * time.Millisecond
	bytesReceived := int64(1024)

	metrics.RecordSuccess(responseTime, bytesReceived, 5)
	metrics.RecordFailure()

	stats := metrics.GetStats()

	// Verify all fields are copied correctly
	assert.Equal(t, int64(1), stats.SuccessfulRequests)
	assert.Equal(t, int64(1), stats.FailedRequests)
	assert.Equal(t, int64(2), stats.TotalRequests)
	assert.Equal(t, responseTime, stats.AverageResponseTime)
	assert.Equal(t, responseTime, stats.FastestResponse)
	assert.Equal(t, responseTime, stats.SlowestResponse)
	assert.Equal(t, bytesReceived, stats.BytesReceived)
	assert.Equal(t, int64(5), stats.HeadersReceived)
	assert.Equal(t, 1, stats.ConsecutiveFailures)
	assert.Equal(t, 0, stats.ConsecutiveSuccesses)
	assert.Equal(t, StateClosed, stats.CircuitBreakerState)

	// Verify it's a copy, not the original
	stats.SuccessfulRequests = 999
	assert.NotEqual(t, int64(999), metrics.SuccessfulRequests)
}

func TestPeerMetrics_Reset(t *testing.T) {
	metrics := NewPeerMetrics()

	// Add some data
	metrics.RecordSuccess(100*time.Millisecond, 1024, 5)
	metrics.RecordFailure()
	metrics.RecordMaliciousActivity()

	// Verify data exists
	assert.NotEqual(t, int64(0), metrics.TotalRequests)
	assert.NotEqual(t, int64(0), metrics.MaliciousAttempts)

	// Reset
	metrics.Reset()

	// Verify reset to initial state
	assert.Equal(t, int64(0), metrics.SuccessfulRequests)
	assert.Equal(t, int64(0), metrics.FailedRequests)
	assert.Equal(t, int64(0), metrics.TotalRequests)
	assert.Equal(t, int64(0), metrics.MaliciousAttempts)
	assert.Equal(t, int64(0), metrics.InvalidDataReceived)
	assert.Equal(t, float64(1.0), metrics.TrustScore)
	assert.Equal(t, time.Hour, metrics.FastestResponse)
	assert.Equal(t, StateClosed, metrics.CircuitBreakerState)
}

func TestPeerMetrics_ConcurrencyThreadSafety(t *testing.T) {
	// Test mutex functionality by calling methods in sequence
	metrics := NewPeerMetrics()

	// Verify mutex protects data by checking consistent state
	metrics.RecordSuccess(100*time.Millisecond, 1024, 5)
	metrics.RecordFailure()
	stats := metrics.GetStats()
	assert.Equal(t, stats.SuccessfulRequests+stats.FailedRequests, stats.TotalRequests)
	assert.True(t, stats.TrustScore >= 0 && stats.TrustScore <= 1)
}

func TestNewPeerMetricsCollection(t *testing.T) {
	collection := NewPeerMetricsCollection()

	assert.NotNil(t, collection)
	assert.NotNil(t, collection.PeerMetrics)
	assert.Equal(t, 0, len(collection.PeerMetrics))
}

func TestPeerMetricsCollection_GetOrCreate(t *testing.T) {
	collection := NewPeerMetricsCollection()
	peerURL := "http://peer1.example.com"

	// First call should create new metrics
	metrics1 := collection.GetOrCreate(peerURL)
	assert.NotNil(t, metrics1)
	assert.Equal(t, 1, len(collection.PeerMetrics))

	// Second call should return existing metrics
	metrics2 := collection.GetOrCreate(peerURL)
	assert.Same(t, metrics1, metrics2) // Should be the same instance
	assert.Equal(t, 1, len(collection.PeerMetrics))

	// Different peer should create new metrics
	metrics3 := collection.GetOrCreate("http://peer2.example.com")
	assert.NotSame(t, metrics1, metrics3)
	assert.Equal(t, 2, len(collection.PeerMetrics))
}

func TestPeerMetricsCollection_GetHealthyPeers(t *testing.T) {
	collection := NewPeerMetricsCollection()

	// Add healthy peer
	healthyPeer := "http://healthy.example.com"
	healthyMetrics := collection.GetOrCreate(healthyPeer)
	healthyMetrics.RecordSuccess(100*time.Millisecond, 1024, 1)

	// Add unhealthy peer (recent failure)
	unhealthyPeer := "http://unhealthy.example.com"
	unhealthyMetrics := collection.GetOrCreate(unhealthyPeer)
	unhealthyMetrics.RecordFailure()

	// Add peer with circuit breaker open
	openPeer := "http://open.example.com"
	openMetrics := collection.GetOrCreate(openPeer)
	openMetrics.RecordSuccess(100*time.Millisecond, 1024, 1)
	openMetrics.CircuitBreakerState = StateOpen

	healthyPeers := collection.GetHealthyPeers()

	assert.Equal(t, 1, len(healthyPeers))
	assert.Contains(t, healthyPeers, healthyPeer)
	assert.NotContains(t, healthyPeers, unhealthyPeer)
	assert.NotContains(t, healthyPeers, openPeer)
}

func TestPeerMetricsCollection_GetBestPeer(t *testing.T) {
	collection := NewPeerMetricsCollection()

	// Add peer with good performance
	goodPeer := "http://good.example.com"
	goodMetrics := collection.GetOrCreate(goodPeer)
	goodMetrics.RecordSuccess(50*time.Millisecond, 1024, 1) // Fast response
	goodMetrics.TrustScore = 0.9

	// Add peer with slower performance
	slowPeer := "http://slow.example.com"
	slowMetrics := collection.GetOrCreate(slowPeer)
	slowMetrics.RecordSuccess(200*time.Millisecond, 1024, 1) // Slower response
	slowMetrics.TrustScore = 0.8

	// Add unhealthy peer
	unhealthyPeer := "http://unhealthy.example.com"
	unhealthyMetrics := collection.GetOrCreate(unhealthyPeer)
	unhealthyMetrics.RecordFailure()

	bestPeer := collection.GetBestPeer()

	assert.Equal(t, goodPeer, bestPeer)
}

func TestPeerMetricsCollection_GetBestPeer_NoHealthyPeers(t *testing.T) {
	collection := NewPeerMetricsCollection()

	// Add only unhealthy peers
	unhealthyPeer := "http://unhealthy.example.com"
	unhealthyMetrics := collection.GetOrCreate(unhealthyPeer)
	unhealthyMetrics.RecordFailure()

	bestPeer := collection.GetBestPeer()

	assert.Equal(t, "", bestPeer) // Should return empty string when no healthy peers
}

func TestPeerMetricsCollection_GetBestPeer_EmptyCollection(t *testing.T) {
	collection := NewPeerMetricsCollection()

	bestPeer := collection.GetBestPeer()

	assert.Equal(t, "", bestPeer) // Should return empty string for empty collection
}

func TestPeerMetricsCollection_ConcurrencyThreadSafety(t *testing.T) {
	// Test mutex functionality by calling methods in sequence
	collection := NewPeerMetricsCollection()

	// Verify mutex protects collection operations
	peer1 := "http://peer1.example.com"
	peer2 := "http://peer2.example.com"

	metrics1 := collection.GetOrCreate(peer1)
	metrics2 := collection.GetOrCreate(peer2)
	metrics1.RecordSuccess(100*time.Millisecond, 1024, 1)
	metrics2.RecordSuccess(50*time.Millisecond, 2048, 2)

	healthyPeers := collection.GetHealthyPeers()
	assert.Len(t, healthyPeers, 2)

	bestPeer := collection.GetBestPeer()
	assert.NotEmpty(t, bestPeer)
}

func TestPeerMetrics_TrustScoreBounds(t *testing.T) {
	metrics := NewPeerMetrics()

	// Test trust score can't go above 1.0
	for range 100 {
		metrics.RecordSuccess(100*time.Millisecond, 1024, 1)
	}
	assert.LessOrEqual(t, metrics.TrustScore, 1.0)

	// Test trust score can't go below 0.0
	for range 100 {
		metrics.RecordFailure()
	}
	assert.GreaterOrEqual(t, metrics.TrustScore, 0.0)
}

func TestPeerMetrics_AverageResponseTimeCalculation(t *testing.T) {
	metrics := NewPeerMetrics()

	// Test initial average
	metrics.RecordSuccess(100*time.Millisecond, 1024, 1)
	assert.Equal(t, 100*time.Millisecond, metrics.AverageResponseTime)

	// Test weighted average calculation
	oldAvg := metrics.AverageResponseTime
	metrics.RecordSuccess(200*time.Millisecond, 1024, 1)

	// Should be weighted: oldAvg * 0.9 + newTime * 0.1
	expectedAvg := time.Duration(float64(oldAvg)*0.9 + float64(200*time.Millisecond)*0.1)
	assert.Equal(t, expectedAvg, metrics.AverageResponseTime)
}
