package centrifugo

import (
	"context"
	"testing"
	"time"

	"github.com/alicebob/miniredis/v2"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/zeromicro/go-zero/core/stores/redis"
)

func setupOnlineTestRedis(t *testing.T) (*redis.Redis, func()) {
	mr, err := miniredis.Run()
	require.NoError(t, err)

	rds, err := redis.NewRedis(redis.RedisConf{
		Host: mr.Addr(),
		Type: "node",
	})
	require.NoError(t, err)

	return rds, func() {
		mr.Close()
	}
}

func TestNewRedisOnlineStore(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)

	assert.NotNil(t, store)
	assert.Equal(t, "online:", store.keyPrefix)
}

func TestNewRedisOnlineStoreWithPrefix(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStoreWithPrefix(rds, "custom:online:")

	assert.NotNil(t, store)
	assert.Equal(t, "custom:online:", store.keyPrefix)
}

func TestSetOnline(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)
	ctx := context.Background()

	userID := "user-123"
	ttl := 5 * time.Minute

	// 設置在線
	err := store.SetOnline(ctx, userID, ttl)
	require.NoError(t, err)

	// 檢查是否在線
	online, err := store.IsOnline(ctx, userID)
	require.NoError(t, err)
	assert.True(t, online)
}

func TestSetOffline(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)
	ctx := context.Background()

	userID := "user-123"

	// 先設置在線
	err := store.SetOnline(ctx, userID, 5*time.Minute)
	require.NoError(t, err)

	// 設置離線
	err = store.SetOffline(ctx, userID)
	require.NoError(t, err)

	// 檢查是否離線
	online, err := store.IsOnline(ctx, userID)
	require.NoError(t, err)
	assert.False(t, online)
}

func TestIsOnline_NotOnline(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)
	ctx := context.Background()

	// 未設置在線的用戶應該返回 false
	online, err := store.IsOnline(ctx, "non-existent-user")
	require.NoError(t, err)
	assert.False(t, online)
}

func TestGetOnlineUsers(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)
	ctx := context.Background()

	// 設置一些用戶在線
	err := store.SetOnline(ctx, "user-1", 5*time.Minute)
	require.NoError(t, err)
	err = store.SetOnline(ctx, "user-3", 5*time.Minute)
	require.NoError(t, err)

	// 批量獲取在線狀態
	userIDs := []string{"user-1", "user-2", "user-3"}
	status, err := store.GetOnlineUsers(ctx, userIDs)
	require.NoError(t, err)

	assert.True(t, status["user-1"])
	assert.False(t, status["user-2"])
	assert.True(t, status["user-3"])
}

func TestGetOnlineUsers_Empty(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)
	ctx := context.Background()

	// 空列表應該返回空 map
	status, err := store.GetOnlineUsers(ctx, []string{})
	require.NoError(t, err)
	assert.Empty(t, status)
}

func TestIncrClient(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)
	ctx := context.Background()

	userID := "user-123"

	// 第一次增加
	count, err := store.IncrClient(ctx, userID)
	require.NoError(t, err)
	assert.Equal(t, int64(1), count)

	// 第二次增加
	count, err = store.IncrClient(ctx, userID)
	require.NoError(t, err)
	assert.Equal(t, int64(2), count)

	// 獲取連線數
	count, err = store.GetClientCount(ctx, userID)
	require.NoError(t, err)
	assert.Equal(t, int64(2), count)
}

func TestDecrClient(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)
	ctx := context.Background()

	userID := "user-123"

	// 先增加到 2
	_, err := store.IncrClient(ctx, userID)
	require.NoError(t, err)
	_, err = store.IncrClient(ctx, userID)
	require.NoError(t, err)

	// 減少一次
	count, err := store.DecrClient(ctx, userID)
	require.NoError(t, err)
	assert.Equal(t, int64(1), count)

	// 再減少一次
	count, err = store.DecrClient(ctx, userID)
	require.NoError(t, err)
	assert.Equal(t, int64(0), count)
}

func TestDecrClient_NegativeProtection(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)
	ctx := context.Background()

	userID := "user-123"

	// 直接減少（沒有先增加）
	count, err := store.DecrClient(ctx, userID)
	require.NoError(t, err)
	assert.Equal(t, int64(0), count) // 應該被保護為 0

	// 再次減少
	count, err = store.DecrClient(ctx, userID)
	require.NoError(t, err)
	assert.Equal(t, int64(0), count) // 仍然是 0
}

func TestGetClientCount_NoClient(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)
	ctx := context.Background()

	// 未設置連線數的用戶應該返回 0
	count, err := store.GetClientCount(ctx, "non-existent-user")
	require.NoError(t, err)
	assert.Equal(t, int64(0), count)
}

func TestSetOffline_ClearsClientCount(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)
	ctx := context.Background()

	userID := "user-123"

	// 設置在線和連線數
	err := store.SetOnline(ctx, userID, 5*time.Minute)
	require.NoError(t, err)
	_, err = store.IncrClient(ctx, userID)
	require.NoError(t, err)
	_, err = store.IncrClient(ctx, userID)
	require.NoError(t, err)

	// 設置離線
	err = store.SetOffline(ctx, userID)
	require.NoError(t, err)

	// 連線數也應該被清除
	count, err := store.GetClientCount(ctx, userID)
	require.NoError(t, err)
	assert.Equal(t, int64(0), count)
}

func TestKeyGeneration_OnlineStore(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStoreWithPrefix(rds, "test:")

	// 測試 key 生成
	assert.Equal(t, "test:user-123", store.key("user-123"))
	assert.Equal(t, "test:clients:user-456", store.clientCountKey("user-456"))
}

func TestOnlineStore_ImplementsInterface(t *testing.T) {
	rds, cleanup := setupOnlineTestRedis(t)
	defer cleanup()

	store := NewRedisOnlineStore(rds)

	// 確保 RedisOnlineStore 實現了 OnlineStore 介面
	var _ OnlineStore = store
}

