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 setupServiceTestRedis(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 TestNewService(t *testing.T) {
	rds, cleanup := setupServiceTestRedis(t)
	defer cleanup()

	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
		Redis:       rds,
	})

	assert.NotNil(t, svc)
	assert.NotNil(t, svc.Client())
	assert.NotNil(t, svc.Token())
	assert.NotNil(t, svc.Blacklist())
	assert.NotNil(t, svc.Online())
}

func TestNewService_WithoutRedis(t *testing.T) {
	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
		Redis:       nil, // 沒有 Redis
	})

	assert.NotNil(t, svc)
	assert.NotNil(t, svc.Client())
	assert.NotNil(t, svc.Token())
	assert.Nil(t, svc.Blacklist()) // 應該為 nil
	assert.Nil(t, svc.Online())    // 應該為 nil
}

func TestNewService_DefaultValues(t *testing.T) {
	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
	})

	assert.NotNil(t, svc)
	// 檢查預設值已被應用（通過生成 Token 間接驗證）
	token, err := svc.GenerateToken("user-123")
	require.NoError(t, err)
	assert.NotEmpty(t, token)
}

func TestNewService_WithCustomConfig(t *testing.T) {
	rds, cleanup := setupServiceTestRedis(t)
	defer cleanup()

	customExpire := 24 * time.Hour
	customTTL := 10 * time.Minute
	customPrefix := "custom:"

	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
		TokenExpire: customExpire,
		Redis:       rds,
		OnlineTTL:   customTTL,
		KeyPrefix:   customPrefix,
	})

	assert.NotNil(t, svc)
}

func TestService_GenerateToken(t *testing.T) {
	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
	})

	token, err := svc.GenerateToken("user-123")

	require.NoError(t, err)
	assert.NotEmpty(t, token)
}

func TestService_GenerateTokenWithInfo(t *testing.T) {
	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
	})

	info := map[string]interface{}{
		"name":   "Daniel",
		"avatar": "https://example.com/avatar.jpg",
	}

	token, err := svc.GenerateTokenWithInfo("user-123", info)

	require.NoError(t, err)
	assert.NotEmpty(t, token)
}

func TestService_IsUserOnline_WithoutRedis(t *testing.T) {
	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
		Redis:       nil,
	})

	_, err := svc.IsUserOnline(context.Background(), "user-123")

	assert.Error(t, err)
	assert.ErrorIs(t, err, ErrOnlineStoreNotConfigured)
}

func TestService_GetUsersOnlineStatus_WithoutRedis(t *testing.T) {
	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
		Redis:       nil,
	})

	_, err := svc.GetUsersOnlineStatus(context.Background(), []string{"user-1", "user-2"})

	assert.Error(t, err)
	assert.ErrorIs(t, err, ErrOnlineStoreNotConfigured)
}

func TestService_IsUserOnline_WithRedis(t *testing.T) {
	rds, cleanup := setupServiceTestRedis(t)
	defer cleanup()

	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
		Redis:       rds,
	})

	ctx := context.Background()
	userID := "user-123"

	// 初始狀態應該是離線
	online, err := svc.IsUserOnline(ctx, userID)
	require.NoError(t, err)
	assert.False(t, online)

	// 處理連線事件
	err = svc.Online().HandleConnect(ctx, userID)
	require.NoError(t, err)

	// 現在應該在線
	online, err = svc.IsUserOnline(ctx, userID)
	require.NoError(t, err)
	assert.True(t, online)

	// 處理斷線事件
	err = svc.Online().HandleDisconnect(ctx, userID)
	require.NoError(t, err)

	// 現在應該離線
	online, err = svc.IsUserOnline(ctx, userID)
	require.NoError(t, err)
	assert.False(t, online)
}

func TestService_GetUsersOnlineStatus_WithRedis(t *testing.T) {
	rds, cleanup := setupServiceTestRedis(t)
	defer cleanup()

	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
		Redis:       rds,
	})

	ctx := context.Background()

	// 設置一些用戶在線
	err := svc.Online().HandleConnect(ctx, "user-1")
	require.NoError(t, err)
	err = svc.Online().HandleConnect(ctx, "user-3")
	require.NoError(t, err)

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

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

func TestService_Blacklist_Integration(t *testing.T) {
	rds, cleanup := setupServiceTestRedis(t)
	defer cleanup()

	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
		Redis:       rds,
	})

	ctx := context.Background()
	userID := "user-123"

	// 撤銷用戶所有 Token
	err := svc.Blacklist().RevokeUserTokens(ctx, userID)
	require.NoError(t, err)

	// 獲取版本
	version, err := svc.Blacklist().GetUserTokenVersion(ctx, userID)
	require.NoError(t, err)
	assert.Greater(t, version, int64(0))

	// 檢查舊版本無效
	valid, err := svc.Blacklist().IsTokenVersionValid(ctx, userID, version-1)
	require.NoError(t, err)
	assert.False(t, valid)

	// 檢查當前版本有效
	valid, err = svc.Blacklist().IsTokenVersionValid(ctx, userID, version)
	require.NoError(t, err)
	assert.True(t, valid)
}

func TestService_MultipleConnections(t *testing.T) {
	rds, cleanup := setupServiceTestRedis(t)
	defer cleanup()

	svc := NewService(ServiceConfig{
		APIURL:      "http://localhost:8000",
		APIKey:      "test-api-key",
		TokenSecret: "test-secret",
		Redis:       rds,
	})

	ctx := context.Background()
	userID := "user-123"

	// 模擬多個設備連線
	err := svc.Online().HandleConnect(ctx, userID)
	require.NoError(t, err)
	err = svc.Online().HandleConnect(ctx, userID)
	require.NoError(t, err)
	err = svc.Online().HandleConnect(ctx, userID)
	require.NoError(t, err)

	// 用戶應該在線
	online, err := svc.IsUserOnline(ctx, userID)
	require.NoError(t, err)
	assert.True(t, online)

	// 斷開一個設備
	err = svc.Online().HandleDisconnect(ctx, userID)
	require.NoError(t, err)

	// 用戶仍然在線（還有 2 個連線）
	online, err = svc.IsUserOnline(ctx, userID)
	require.NoError(t, err)
	assert.True(t, online)

	// 斷開剩餘設備
	err = svc.Online().HandleDisconnect(ctx, userID)
	require.NoError(t, err)
	err = svc.Online().HandleDisconnect(ctx, userID)
	require.NoError(t, err)

	// 用戶現在離線
	online, err = svc.IsUserOnline(ctx, userID)
	require.NoError(t, err)
	assert.False(t, online)
}

