package monitoring

import (
	"testing"
	"time"

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

func TestLogFilters_GetLimit(t *testing.T) {
	tests := []struct {
		name     string
		limit    int
		expected int
	}{
		{"default for zero", 0, 100},
		{"default for negative", -5, 100},
		{"capped at 1000", 2000, 1000},
		{"exactly 1000", 1000, 1000},
		{"normal value", 50, 50},
		{"minimum valid", 1, 1},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			f := LogFilters{Limit: tc.limit}
			assert.Equal(t, tc.expected, f.GetLimit())
		})
	}
}

func TestHTTPStatsCollector_Record_And_GetStats(t *testing.T) {
	c := NewHTTPStatsCollector()

	c.Record("GET /api/tasks/", 100*time.Millisecond, 200)
	c.Record("GET /api/tasks/", 200*time.Millisecond, 200)
	c.Record("POST /api/tasks/", 50*time.Millisecond, 201)
	c.Record("GET /api/tasks/", 300*time.Millisecond, 500)

	stats := c.GetStats()

	assert.Equal(t, int64(4), stats.RequestsTotal)
	assert.True(t, stats.RequestsPerMinute > 0)

	// Check endpoint stats
	taskStats, ok := stats.ByEndpoint["GET /api/tasks/"]
	assert.True(t, ok)
	assert.Equal(t, int64(3), taskStats.Count)
	assert.True(t, taskStats.AvgLatencyMs > 0)

	postStats, ok := stats.ByEndpoint["POST /api/tasks/"]
	assert.True(t, ok)
	assert.Equal(t, int64(1), postStats.Count)

	// Check status codes
	assert.Equal(t, int64(2), stats.ByStatusCode[200])
	assert.Equal(t, int64(1), stats.ByStatusCode[201])
	assert.Equal(t, int64(1), stats.ByStatusCode[500])

	// Error rate should include 500 status
	assert.True(t, stats.ErrorRate > 0)
}

func TestHTTPStatsCollector_Reset(t *testing.T) {
	c := NewHTTPStatsCollector()
	c.Record("GET /api/tasks/", 100*time.Millisecond, 200)

	c.Reset()

	stats := c.GetStats()
	assert.Equal(t, int64(0), stats.RequestsTotal)
	assert.Empty(t, stats.ByEndpoint)
	assert.Empty(t, stats.ByStatusCode)
}

func TestHTTPStatsCollector_ErrorRate(t *testing.T) {
	c := NewHTTPStatsCollector()
	c.Record("GET /api/tasks/", 10*time.Millisecond, 200)
	c.Record("GET /api/tasks/", 10*time.Millisecond, 400)
	c.Record("GET /api/tasks/", 10*time.Millisecond, 500)

	stats := c.GetStats()
	ep := stats.ByEndpoint["GET /api/tasks/"]

	// 2 out of 3 are errors (400 and 500)
	assert.InDelta(t, 2.0/3.0, ep.ErrorRate, 0.001)
}

func TestHTTPStatsCollector_P95(t *testing.T) {
	c := NewHTTPStatsCollector()

	// Record 100 requests with increasing latencies
	for i := 1; i <= 100; i++ {
		c.Record("GET /api/test/", time.Duration(i)*time.Millisecond, 200)
	}

	stats := c.GetStats()
	ep := stats.ByEndpoint["GET /api/test/"]
	// P95 should be around 95ms
	assert.True(t, ep.P95LatencyMs >= 90, "P95 should be >= 90ms, got %f", ep.P95LatencyMs)
}

func TestHTTPStatsCollector_EmptyStats(t *testing.T) {
	c := NewHTTPStatsCollector()
	stats := c.GetStats()

	assert.Equal(t, int64(0), stats.RequestsTotal)
	assert.Equal(t, float64(0), stats.AvgLatencyMs)
	assert.Equal(t, float64(0), stats.ErrorRate)
	assert.Equal(t, float64(0), stats.RequestsPerMinute)
}

func TestHTTPStatsCollector_EndpointOverflow(t *testing.T) {
	c := NewHTTPStatsCollector()

	// Fill up to maxEndpoints unique endpoints
	for i := 0; i < maxEndpoints+10; i++ {
		endpoint := "GET /api/test/" + string(rune('A'+i%26)) + string(rune('0'+i/26))
		c.Record(endpoint, 10*time.Millisecond, 200)
	}

	stats := c.GetStats()
	// Should have at most maxEndpoints + 1 (the OTHER bucket)
	assert.LessOrEqual(t, len(stats.ByEndpoint), maxEndpoints+1)
}

func TestWSMessageConstants(t *testing.T) {
	assert.Equal(t, "log", WSMessageTypeLog)
	assert.Equal(t, "stats", WSMessageTypeStats)
}

func TestRedisLogWriter_Write_Disabled(t *testing.T) {
	// Create a writer with a nil buffer -- won't actually push to Redis
	// but we can test the enabled/disabled logic
	w := &RedisLogWriter{
		process: "api",
		ch:      make(chan LogEntry, writerChannelSize),
		done:    make(chan struct{}),
	}
	w.enabled.Store(false)

	// Start drain loop (reads from channel)
	go func() {
		defer close(w.done)
		for range w.ch {
		}
	}()

	n, err := w.Write([]byte(`{"level":"info","message":"test"}`))
	assert.NoError(t, err)
	assert.Greater(t, n, 0)

	// Channel should be empty since writer is disabled
	assert.Equal(t, 0, len(w.ch))

	close(w.ch)
	<-w.done
}

func TestRedisLogWriter_Write_Enabled(t *testing.T) {
	w := &RedisLogWriter{
		process: "api",
		ch:      make(chan LogEntry, writerChannelSize),
		done:    make(chan struct{}),
	}
	w.enabled.Store(true)

	go func() {
		defer close(w.done)
		for range w.ch {
		}
	}()

	n, err := w.Write([]byte(`{"level":"info","message":"hello","caller":"main.go:10"}`))
	assert.NoError(t, err)
	assert.Greater(t, n, 0)

	// Give the goroutine a moment, then close
	close(w.ch)
	<-w.done
}

func TestRedisLogWriter_Write_InvalidJSON(t *testing.T) {
	w := &RedisLogWriter{
		process: "api",
		ch:      make(chan LogEntry, writerChannelSize),
		done:    make(chan struct{}),
	}
	w.enabled.Store(true)

	go func() {
		defer close(w.done)
		for range w.ch {
		}
	}()

	// Non-JSON input should be silently skipped
	n, err := w.Write([]byte("not json at all"))
	assert.NoError(t, err)
	assert.Greater(t, n, 0)
	assert.Equal(t, 0, len(w.ch))

	close(w.ch)
	<-w.done
}

func TestRedisLogWriter_SetEnabled_IsEnabled(t *testing.T) {
	w := &RedisLogWriter{
		process: "api",
		ch:      make(chan LogEntry, 1),
		done:    make(chan struct{}),
	}
	w.enabled.Store(true)

	assert.True(t, w.IsEnabled())
	w.SetEnabled(false)
	assert.False(t, w.IsEnabled())
	w.SetEnabled(true)
	assert.True(t, w.IsEnabled())
}

func TestCollector_Stop_MultipleCallsSafe(t *testing.T) {
	c := &Collector{
		process:   "api",
		startTime: time.Now(),
		stopChan:  make(chan struct{}),
	}

	// Should not panic on multiple calls
	c.Stop()
	c.Stop()
	c.Stop()
}
