package tuning

import (
	"testing"
	"time"

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

func TestTimeoutAnalyzer(t *testing.T) {
	ta := NewTimeoutAnalyzer(t.TempDir())

	// Record some metrics
	ta.RecordExecution("activity1", 1*time.Second, true, nil)
	ta.RecordExecution("activity1", 2*time.Second, true, nil)
	ta.RecordExecution("activity1", 3*time.Second, true, nil)

	assert.Equal(t, 3, ta.GetMetricsCount())
}

func TestAnalyzeMetrics(t *testing.T) {
	ta := NewTimeoutAnalyzer(t.TempDir())

	// Record metrics with P95 around 9s
	for i := 1; i <= 20; i++ {
		duration := time.Duration(i) * time.Second
		ta.RecordExecution("activity1", duration, true, nil)
	}

	currentTimeouts := map[string]time.Duration{
		"activity1": 5 * time.Second,
	}

	recommendations, err := ta.Analyze(currentTimeouts)
	assert.NoError(t, err)
	assert.Greater(t, len(recommendations), 0)

	rec := recommendations[0]
	assert.Equal(t, "activity1", rec.ActivityType)
	assert.Equal(t, 5*time.Second, rec.CurrentTimeout)
	assert.Greater(t, rec.RecommendedTimeout, rec.CurrentTimeout)
}

func TestAnalyzeWithFailures(t *testing.T) {
	ta := NewTimeoutAnalyzer(t.TempDir())

	// Record some failures
	for i := 0; i < 5; i++ {
		ta.RecordExecution("slow_activity", 10*time.Second, false, assert.AnError)
	}

	currentTimeouts := map[string]time.Duration{
		"slow_activity": 5 * time.Second,
	}

	recommendations, err := ta.Analyze(currentTimeouts)
	assert.NoError(t, err)

	if len(recommendations) > 0 {
		rec := recommendations[0]
		assert.Equal(t, 5, rec.FailureCount)
		assert.Greater(t, rec.RecommendedTimeout, rec.CurrentTimeout)
	}
}

func TestCalculatePercentile(t *testing.T) {
	durations := []time.Duration{
		1 * time.Second,
		2 * time.Second,
		3 * time.Second,
		4 * time.Second,
		5 * time.Second,
		6 * time.Second,
		7 * time.Second,
		8 * time.Second,
		9 * time.Second,
		10 * time.Second,
	}

	p95 := calculatePercentile(durations, 0.95)
	assert.NotZero(t, p95)
	assert.LessOrEqual(t, p95, 10*time.Second)

	p99 := calculatePercentile(durations, 0.99)
	assert.NotZero(t, p99)
	assert.GreaterOrEqual(t, p99, p95)
}

func TestCalculateAverage(t *testing.T) {
	durations := []time.Duration{
		1 * time.Second,
		2 * time.Second,
		3 * time.Second,
	}

	avg := calculateAverage(durations)
	assert.Equal(t, 2*time.Second, avg)
}

func TestCalculateConfidence(t *testing.T) {
	// Perfect success
	conf := calculateConfidence(100, 0)
	assert.Equal(t, 1.0, conf)

	// 50% success
	conf = calculateConfidence(50, 50)
	assert.Greater(t, conf, 0.0)
	assert.Less(t, conf, 1.0)

	// All failures
	conf = calculateConfidence(0, 100)
	assert.Less(t, conf, 1.0)
}

func TestGroupMetricsByActivity(t *testing.T) {
	ta := NewTimeoutAnalyzer(t.TempDir())

	ta.RecordExecution("activity1", 1*time.Second, true, nil)
	ta.RecordExecution("activity1", 2*time.Second, true, nil)
	ta.RecordExecution("activity2", 3*time.Second, true, nil)

	groups := ta.groupMetricsByActivity()
	assert.Equal(t, 2, len(groups))
	assert.Equal(t, 2, len(groups["activity1"]))
	assert.Equal(t, 1, len(groups["activity2"]))
}

func TestClearMetrics(t *testing.T) {
	ta := NewTimeoutAnalyzer(t.TempDir())

	ta.RecordExecution("activity1", 1*time.Second, true, nil)
	assert.Equal(t, 1, ta.GetMetricsCount())

	ta.ClearMetrics()
	assert.Equal(t, 0, ta.GetMetricsCount())
}

func TestGetRecommendations(t *testing.T) {
	ta := NewTimeoutAnalyzer(t.TempDir())

	ta.RecordExecution("activity1", 1*time.Second, true, nil)
	ta.RecordExecution("activity1", 2*time.Second, true, nil)

	currentTimeouts := map[string]time.Duration{
		"activity1": 5 * time.Second,
	}

	ta.Analyze(currentTimeouts)
	recs := ta.GetRecommendations()
	assert.IsType(t, make(map[string]*TimeoutRecommendation), recs)
}

func TestRecommendationStructure(t *testing.T) {
	ta := NewTimeoutAnalyzer(t.TempDir())

	// Record consistent executions
	for i := 0; i < 10; i++ {
		ta.RecordExecution("activity1", 5*time.Second, true, nil)
	}

	currentTimeouts := map[string]time.Duration{
		"activity1": 2 * time.Second, // Too tight
	}

	recommendations, err := ta.Analyze(currentTimeouts)
	assert.NoError(t, err)

	if len(recommendations) > 0 {
		rec := recommendations[0]
		assert.NotEmpty(t, rec.ActivityType)
		assert.NotZero(t, rec.CurrentTimeout)
		assert.NotZero(t, rec.P95Duration)
		assert.Greater(t, rec.SuccessCount, 0)
		assert.NotEmpty(t, rec.Reason)
		assert.Greater(t, rec.Confidence, 0.0)
	}
}

func TestMultipleActivities(t *testing.T) {
	ta := NewTimeoutAnalyzer(t.TempDir())

	// Record metrics for multiple activities
	for i := 0; i < 10; i++ {
		ta.RecordExecution("fast_activity", time.Duration(i+1)*time.Second, true, nil)
		ta.RecordExecution("slow_activity", time.Duration(i+10)*time.Second, true, nil)
	}

	currentTimeouts := map[string]time.Duration{
		"fast_activity": 3 * time.Second,
		"slow_activity": 5 * time.Second,
	}

	recommendations, err := ta.Analyze(currentTimeouts)
	assert.NoError(t, err)
	assert.Greater(t, len(recommendations), 0)

	// Check that we get recommendations for both activities
	hasSlowActivity := false
	for _, rec := range recommendations {
		if rec.ActivityType == "slow_activity" {
			hasSlowActivity = true
			break
		}
	}
	assert.True(t, hasSlowActivity)
}

func TestEmptyMetrics(t *testing.T) {
	ta := NewTimeoutAnalyzer(t.TempDir())

	currentTimeouts := map[string]time.Duration{
		"activity1": 5 * time.Second,
	}

	recommendations, err := ta.Analyze(currentTimeouts)
	assert.NoError(t, err)
	assert.Equal(t, 0, len(recommendations))
}

func TestAllFailures(t *testing.T) {
	ta := NewTimeoutAnalyzer(t.TempDir())

	// Record only failures
	for i := 0; i < 5; i++ {
		ta.RecordExecution("activity1", 1*time.Second, false, assert.AnError)
	}

	currentTimeouts := map[string]time.Duration{
		"activity1": 5 * time.Second,
	}

	recommendations, err := ta.Analyze(currentTimeouts)
	assert.NoError(t, err)

	// Should recommend increase despite no successes
	if len(recommendations) > 0 {
		rec := recommendations[0]
		assert.Equal(t, 5, rec.FailureCount)
		assert.Equal(t, 0, rec.SuccessCount)
	}
}

func TestSaveAndLoadMetrics(t *testing.T) {
	tmpDir := t.TempDir()
	ta1 := NewTimeoutAnalyzer(tmpDir)

	// Record and save
	ta1.RecordExecution("activity1", 1*time.Second, true, nil)
	ta1.RecordExecution("activity1", 2*time.Second, true, nil)

	err := ta1.SaveMetrics()
	assert.NoError(t, err)

	// Load in new analyzer
	ta2 := NewTimeoutAnalyzer(tmpDir)
	err = ta2.LoadMetrics()
	assert.NoError(t, err)

	assert.Equal(t, ta1.GetMetricsCount(), ta2.GetMetricsCount())
}

func TestSaveRecommendations(t *testing.T) {
	tmpDir := t.TempDir()
	ta := NewTimeoutAnalyzer(tmpDir)

	recommendations := []TimeoutRecommendation{
		{
			ActivityType:       "activity1",
			CurrentTimeout:     5 * time.Second,
			RecommendedTimeout: 10 * time.Second,
			Confidence:         0.95,
			Timestamp:          time.Now(),
		},
	}

	err := ta.SaveRecommendations(recommendations)
	assert.NoError(t, err)
}
