package middleware

import (
	"testing"
	"time"

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

	"github.com/treytartt/honeydue-api/internal/models"
)

func TestUserCache_SetAndGet(t *testing.T) {
	cache := NewUserCache(1 * time.Minute)

	user := &models.User{Username: "testuser", Email: "test@test.com"}
	user.ID = 1

	cache.Set(user)

	cached := cache.Get(1)
	require.NotNil(t, cached)
	assert.Equal(t, "testuser", cached.Username)
	assert.Equal(t, "test@test.com", cached.Email)
}

func TestUserCache_GetNonExistent_ReturnsNil(t *testing.T) {
	cache := NewUserCache(1 * time.Minute)

	cached := cache.Get(999)
	assert.Nil(t, cached)
}

func TestUserCache_Expired_ReturnsNil(t *testing.T) {
	// Very short TTL
	cache := NewUserCache(1 * time.Millisecond)

	user := &models.User{Username: "expiring_user"}
	user.ID = 1

	cache.Set(user)

	// Wait for expiry
	time.Sleep(5 * time.Millisecond)

	cached := cache.Get(1)
	assert.Nil(t, cached, "expired entry should return nil")
}

func TestUserCache_Invalidate(t *testing.T) {
	cache := NewUserCache(1 * time.Minute)

	user := &models.User{Username: "to_invalidate"}
	user.ID = 1

	cache.Set(user)

	// Verify it's cached
	require.NotNil(t, cache.Get(1))

	// Invalidate
	cache.Invalidate(1)

	// Should be gone
	assert.Nil(t, cache.Get(1))
}

func TestUserCache_ReturnsCopy_NotOriginal(t *testing.T) {
	cache := NewUserCache(1 * time.Minute)

	user := &models.User{Username: "original"}
	user.ID = 1

	cache.Set(user)

	// Modify the returned copy
	cached := cache.Get(1)
	require.NotNil(t, cached)
	cached.Username = "modified"

	// Original cache entry should be unaffected
	cached2 := cache.Get(1)
	require.NotNil(t, cached2)
	assert.Equal(t, "original", cached2.Username, "cache should return a copy, not the original")
}

func TestUserCache_SetCopiesInput(t *testing.T) {
	cache := NewUserCache(1 * time.Minute)

	user := &models.User{Username: "original"}
	user.ID = 1

	cache.Set(user)

	// Modify the input after setting
	user.Username = "modified_after_set"

	// Cache should still have the original value
	cached := cache.Get(1)
	require.NotNil(t, cached)
	assert.Equal(t, "original", cached.Username, "cache should store a copy of the input")
}

func TestUserCache_MultipleUsers(t *testing.T) {
	cache := NewUserCache(1 * time.Minute)

	user1 := &models.User{Username: "user1"}
	user1.ID = 1
	user2 := &models.User{Username: "user2"}
	user2.ID = 2

	cache.Set(user1)
	cache.Set(user2)

	cached1 := cache.Get(1)
	cached2 := cache.Get(2)

	require.NotNil(t, cached1)
	require.NotNil(t, cached2)
	assert.Equal(t, "user1", cached1.Username)
	assert.Equal(t, "user2", cached2.Username)
}

func TestUserCache_OverwriteEntry(t *testing.T) {
	cache := NewUserCache(1 * time.Minute)

	user := &models.User{Username: "original"}
	user.ID = 1

	cache.Set(user)

	// Overwrite with new data
	updated := &models.User{Username: "updated"}
	updated.ID = 1

	cache.Set(updated)

	cached := cache.Get(1)
	require.NotNil(t, cached)
	assert.Equal(t, "updated", cached.Username)
}

func TestTimezoneCache_GetAndCompare_NewEntry(t *testing.T) {
	tc := NewTimezoneCache()

	// First call should return false (not cached yet)
	unchanged := tc.GetAndCompare(1, "America/New_York")
	assert.False(t, unchanged, "first call should indicate a change")
}

func TestTimezoneCache_GetAndCompare_SameValue(t *testing.T) {
	tc := NewTimezoneCache()

	// First call sets the value
	tc.GetAndCompare(1, "America/New_York")

	// Second call with same value should return true (unchanged)
	unchanged := tc.GetAndCompare(1, "America/New_York")
	assert.True(t, unchanged, "same value should indicate no change")
}

func TestTimezoneCache_GetAndCompare_DifferentValue(t *testing.T) {
	tc := NewTimezoneCache()

	// Set initial value
	tc.GetAndCompare(1, "America/New_York")

	// Update to different value
	unchanged := tc.GetAndCompare(1, "America/Chicago")
	assert.False(t, unchanged, "different value should indicate a change")

	// Now the new value is cached
	unchanged = tc.GetAndCompare(1, "America/Chicago")
	assert.True(t, unchanged, "same value should indicate no change")
}

func TestTimezoneCache_GetAndCompare_DifferentUsers(t *testing.T) {
	tc := NewTimezoneCache()

	tc.GetAndCompare(1, "America/New_York")
	tc.GetAndCompare(2, "Europe/London")

	assert.True(t, tc.GetAndCompare(1, "America/New_York"))
	assert.True(t, tc.GetAndCompare(2, "Europe/London"))
	assert.False(t, tc.GetAndCompare(1, "Europe/London"))
}
