package audit

import (
	"strconv"
	"sync"
	"testing"
	"time"

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

func TestHotConfig_NewHotConfig(t *testing.T) {
	cfg := &Config{
		FlushInterval:        time.Second,
		IncludeGetRequests:   true,
		MaxBodySize:          512,
		SensitiveQueryParams: []string{"token", "apiKey"},
		SensitiveBodyFields:  []string{"password"},
	}

	hot := newHotConfig(cfg)

	assert.Equal(t, time.Second, hot.getFlushInterval())
	assert.True(t, hot.getIncludeGetRequests())
	assert.Equal(t, 512, hot.getMaxBodySize())
	assert.NotNil(t, hot.getSanitizer())
}

func TestHotConfig_DefaultValues(t *testing.T) {
	tests := []struct {
		name              string
		flushInterval     time.Duration
		maxBodySize       int
		wantFlushInterval time.Duration
		wantMaxBodySize   int
	}{
		{
			name:              "zero flush interval uses default",
			flushInterval:     0,
			maxBodySize:       512,
			wantFlushInterval: defaultFlushInterval,
			wantMaxBodySize:   512,
		},
		{
			name:              "negative flush interval uses default",
			flushInterval:     -1 * time.Second,
			maxBodySize:       512,
			wantFlushInterval: defaultFlushInterval,
			wantMaxBodySize:   512,
		},
		{
			name:              "zero max body size disables body logging",
			flushInterval:     time.Second,
			maxBodySize:       0,
			wantFlushInterval: time.Second,
			wantMaxBodySize:   0,
		},
		{
			name:              "negative max body size disables body logging",
			flushInterval:     time.Second,
			maxBodySize:       -1,
			wantFlushInterval: time.Second,
			wantMaxBodySize:   0,
		},
		{
			name:              "both zero use defaults",
			flushInterval:     0,
			maxBodySize:       0,
			wantFlushInterval: defaultFlushInterval,
			wantMaxBodySize:   0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cfg := &Config{
				FlushInterval: tt.flushInterval,
				MaxBodySize:   tt.maxBodySize,
			}

			hot := newHotConfig(cfg)

			assert.Equal(t, tt.wantFlushInterval, hot.getFlushInterval())
			assert.Equal(t, tt.wantMaxBodySize, hot.getMaxBodySize())
		})
	}
}

func TestHotConfig_MaxBodySizeTooLarge_DisablesBodyLogging(t *testing.T) {
	// Only relevant on 64-bit platforms where int can exceed int32.
	if strconv.IntSize == 32 {
		t.Skip("int size is 32-bit; cannot represent maxBodySize > int32")
	}

	tooLarge64 := int64(1) << 31 // 2147483648
	hot := newHotConfig(&Config{
		MaxBodySize: int(tooLarge64),
	})
	assert.Equal(t, 0, hot.getMaxBodySize())
}

func TestHotConfig_Update(t *testing.T) {
	// Initial config
	hot := newHotConfig(&Config{
		FlushInterval:      time.Second,
		IncludeGetRequests: false,
		MaxBodySize:        256,
	})

	assert.Equal(t, time.Second, hot.getFlushInterval())
	assert.False(t, hot.getIncludeGetRequests())
	assert.Equal(t, 256, hot.getMaxBodySize())

	// Update with new values
	hot.update(&Config{
		FlushInterval:        500 * time.Millisecond,
		IncludeGetRequests:   true,
		MaxBodySize:          1024,
		SensitiveQueryParams: []string{"secret"},
	})

	assert.Equal(t, 500*time.Millisecond, hot.getFlushInterval())
	assert.True(t, hot.getIncludeGetRequests())
	assert.Equal(t, 1024, hot.getMaxBodySize())
	assert.NotNil(t, hot.getSanitizer())
}

func TestHotConfig_Getters(t *testing.T) {
	cfg := &Config{
		FlushInterval:        2 * time.Second,
		IncludeGetRequests:   true,
		MaxBodySize:          1024,
		SensitiveQueryParams: []string{"token"},
		SensitiveBodyFields:  []string{"password", "secret"},
	}

	hot := newHotConfig(cfg)

	t.Run("getFlushInterval", func(t *testing.T) {
		assert.Equal(t, 2*time.Second, hot.getFlushInterval())
	})

	t.Run("getIncludeGetRequests", func(t *testing.T) {
		assert.True(t, hot.getIncludeGetRequests())
	})

	t.Run("getMaxBodySize", func(t *testing.T) {
		assert.Equal(t, 1024, hot.getMaxBodySize())
	})

	t.Run("getSanitizer", func(t *testing.T) {
		sanitizer := hot.getSanitizer()
		assert.NotNil(t, sanitizer)
	})
}

func TestHotConfig_ConcurrentAccess(t *testing.T) {
	hot := newHotConfig(&Config{
		FlushInterval:      time.Second,
		IncludeGetRequests: false,
		MaxBodySize:        256,
	})

	done := make(chan struct{})
	var wg sync.WaitGroup

	// Concurrent readers
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for {
				select {
				case <-done:
					return
				default:
					_ = hot.getFlushInterval()
					_ = hot.getIncludeGetRequests()
					_ = hot.getMaxBodySize()
					_ = hot.getSanitizer()
				}
			}
		}()
	}

	// Concurrent writer
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i < 100; i++ {
			hot.update(&Config{
				FlushInterval:      time.Duration(i+1) * time.Millisecond * 100,
				IncludeGetRequests: i%2 == 0,
				MaxBodySize:        (i + 1) * 100,
			})
		}
		close(done)
	}()

	// Wait for all goroutines to finish to prevent test pollution
	wg.Wait()
}
