//go:build unit

package service

import (
	"testing"
	"time"

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

// ============ shuffleWithinSortGroups 测试 ============

func TestShuffleWithinSortGroups_Empty(t *testing.T) {
	shuffleWithinSortGroups(nil)
	shuffleWithinSortGroups([]accountWithLoad{})
}

func TestShuffleWithinSortGroups_SingleElement(t *testing.T) {
	accounts := []accountWithLoad{
		{account: &Account{ID: 1, Priority: 1}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
	}
	shuffleWithinSortGroups(accounts)
	require.Equal(t, int64(1), accounts[0].account.ID)
}

func TestShuffleWithinSortGroups_DifferentGroups_OrderPreserved(t *testing.T) {
	now := time.Now()
	earlier := now.Add(-1 * time.Hour)

	accounts := []accountWithLoad{
		{account: &Account{ID: 1, Priority: 1, LastUsedAt: &earlier}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
		{account: &Account{ID: 2, Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 20}},
		{account: &Account{ID: 3, Priority: 2, LastUsedAt: &earlier}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
	}

	// 每个元素都属于不同组（Priority 或 LoadRate 或 LastUsedAt 不同），顺序不变
	for i := 0; i < 20; i++ {
		cpy := make([]accountWithLoad, len(accounts))
		copy(cpy, accounts)
		shuffleWithinSortGroups(cpy)
		require.Equal(t, int64(1), cpy[0].account.ID)
		require.Equal(t, int64(2), cpy[1].account.ID)
		require.Equal(t, int64(3), cpy[2].account.ID)
	}
}

func TestShuffleWithinSortGroups_SameGroup_Shuffled(t *testing.T) {
	now := time.Now()
	// 同一秒的时间戳视为同一组
	sameSecond := time.Unix(now.Unix(), 0)
	sameSecond2 := time.Unix(now.Unix(), 500_000_000) // 同一秒但不同纳秒

	accounts := []accountWithLoad{
		{account: &Account{ID: 1, Priority: 1, LastUsedAt: &sameSecond}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
		{account: &Account{ID: 2, Priority: 1, LastUsedAt: &sameSecond2}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
		{account: &Account{ID: 3, Priority: 1, LastUsedAt: &sameSecond}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
	}

	// 多次执行，验证所有 ID 都出现在第一个位置（说明确实被打乱了）
	seen := map[int64]bool{}
	for i := 0; i < 100; i++ {
		cpy := make([]accountWithLoad, len(accounts))
		copy(cpy, accounts)
		shuffleWithinSortGroups(cpy)
		seen[cpy[0].account.ID] = true
		// 无论怎么打乱，所有 ID 都应在候选中
		ids := map[int64]bool{}
		for _, a := range cpy {
			ids[a.account.ID] = true
		}
		require.True(t, ids[1] && ids[2] && ids[3])
	}
	// 至少 2 个不同的 ID 出现在首位（随机性验证）
	require.GreaterOrEqual(t, len(seen), 2, "shuffle should produce different orderings")
}

func TestShuffleWithinSortGroups_NilLastUsedAt_SameGroup(t *testing.T) {
	accounts := []accountWithLoad{
		{account: &Account{ID: 1, Priority: 1, LastUsedAt: nil}, loadInfo: &AccountLoadInfo{LoadRate: 0}},
		{account: &Account{ID: 2, Priority: 1, LastUsedAt: nil}, loadInfo: &AccountLoadInfo{LoadRate: 0}},
		{account: &Account{ID: 3, Priority: 1, LastUsedAt: nil}, loadInfo: &AccountLoadInfo{LoadRate: 0}},
	}

	seen := map[int64]bool{}
	for i := 0; i < 100; i++ {
		cpy := make([]accountWithLoad, len(accounts))
		copy(cpy, accounts)
		shuffleWithinSortGroups(cpy)
		seen[cpy[0].account.ID] = true
	}
	require.GreaterOrEqual(t, len(seen), 2, "nil LastUsedAt accounts should be shuffled")
}

func TestShuffleWithinSortGroups_MixedGroups(t *testing.T) {
	now := time.Now()
	earlier := now.Add(-1 * time.Hour)
	sameAsNow := time.Unix(now.Unix(), 0)

	// 组1: Priority=1, LoadRate=10, LastUsedAt=earlier (ID 1)  — 单元素组
	// 组2: Priority=1, LoadRate=20, LastUsedAt=now (ID 2, 3)   — 双元素组
	// 组3: Priority=2, LoadRate=10, LastUsedAt=earlier (ID 4)  — 单元素组
	accounts := []accountWithLoad{
		{account: &Account{ID: 1, Priority: 1, LastUsedAt: &earlier}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
		{account: &Account{ID: 2, Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 20}},
		{account: &Account{ID: 3, Priority: 1, LastUsedAt: &sameAsNow}, loadInfo: &AccountLoadInfo{LoadRate: 20}},
		{account: &Account{ID: 4, Priority: 2, LastUsedAt: &earlier}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
	}

	for i := 0; i < 20; i++ {
		cpy := make([]accountWithLoad, len(accounts))
		copy(cpy, accounts)
		shuffleWithinSortGroups(cpy)

		// 组间顺序不变
		require.Equal(t, int64(1), cpy[0].account.ID, "group 1 position fixed")
		require.Equal(t, int64(4), cpy[3].account.ID, "group 3 position fixed")

		// 组2 内部可以打乱，但仍在位置 1 和 2
		mid := map[int64]bool{cpy[1].account.ID: true, cpy[2].account.ID: true}
		require.True(t, mid[2] && mid[3], "group 2 elements should stay in positions 1-2")
	}
}

// ============ shuffleWithinPriorityAndLastUsed 测试 ============

func TestShuffleWithinPriorityAndLastUsed_Empty(t *testing.T) {
	shuffleWithinPriorityAndLastUsed(nil)
	shuffleWithinPriorityAndLastUsed([]*Account{})
}

func TestShuffleWithinPriorityAndLastUsed_SingleElement(t *testing.T) {
	accounts := []*Account{{ID: 1, Priority: 1}}
	shuffleWithinPriorityAndLastUsed(accounts)
	require.Equal(t, int64(1), accounts[0].ID)
}

func TestShuffleWithinPriorityAndLastUsed_SameGroup_Shuffled(t *testing.T) {
	accounts := []*Account{
		{ID: 1, Priority: 1, LastUsedAt: nil},
		{ID: 2, Priority: 1, LastUsedAt: nil},
		{ID: 3, Priority: 1, LastUsedAt: nil},
	}

	seen := map[int64]bool{}
	for i := 0; i < 100; i++ {
		cpy := make([]*Account, len(accounts))
		copy(cpy, accounts)
		shuffleWithinPriorityAndLastUsed(cpy)
		seen[cpy[0].ID] = true
	}
	require.GreaterOrEqual(t, len(seen), 2, "same group should be shuffled")
}

func TestShuffleWithinPriorityAndLastUsed_DifferentPriority_OrderPreserved(t *testing.T) {
	accounts := []*Account{
		{ID: 1, Priority: 1, LastUsedAt: nil},
		{ID: 2, Priority: 2, LastUsedAt: nil},
		{ID: 3, Priority: 3, LastUsedAt: nil},
	}

	for i := 0; i < 20; i++ {
		cpy := make([]*Account, len(accounts))
		copy(cpy, accounts)
		shuffleWithinPriorityAndLastUsed(cpy)
		require.Equal(t, int64(1), cpy[0].ID)
		require.Equal(t, int64(2), cpy[1].ID)
		require.Equal(t, int64(3), cpy[2].ID)
	}
}

func TestShuffleWithinPriorityAndLastUsed_DifferentLastUsedAt_OrderPreserved(t *testing.T) {
	now := time.Now()
	earlier := now.Add(-1 * time.Hour)

	accounts := []*Account{
		{ID: 1, Priority: 1, LastUsedAt: nil},
		{ID: 2, Priority: 1, LastUsedAt: &earlier},
		{ID: 3, Priority: 1, LastUsedAt: &now},
	}

	for i := 0; i < 20; i++ {
		cpy := make([]*Account, len(accounts))
		copy(cpy, accounts)
		shuffleWithinPriorityAndLastUsed(cpy)
		require.Equal(t, int64(1), cpy[0].ID)
		require.Equal(t, int64(2), cpy[1].ID)
		require.Equal(t, int64(3), cpy[2].ID)
	}
}

// ============ sameLastUsedAt 测试 ============

func TestSameLastUsedAt(t *testing.T) {
	now := time.Now()
	sameSecond := time.Unix(now.Unix(), 0)
	sameSecondDiffNano := time.Unix(now.Unix(), 999_999_999)
	differentSecond := now.Add(1 * time.Second)

	t.Run("both nil", func(t *testing.T) {
		require.True(t, sameLastUsedAt(nil, nil))
	})

	t.Run("one nil one not", func(t *testing.T) {
		require.False(t, sameLastUsedAt(nil, &now))
		require.False(t, sameLastUsedAt(&now, nil))
	})

	t.Run("same second different nanoseconds", func(t *testing.T) {
		require.True(t, sameLastUsedAt(&sameSecond, &sameSecondDiffNano))
	})

	t.Run("different seconds", func(t *testing.T) {
		require.False(t, sameLastUsedAt(&now, &differentSecond))
	})

	t.Run("exact same time", func(t *testing.T) {
		require.True(t, sameLastUsedAt(&now, &now))
	})
}

// ============ sameAccountWithLoadGroup 测试 ============

func TestSameAccountWithLoadGroup(t *testing.T) {
	now := time.Now()
	sameSecond := time.Unix(now.Unix(), 0)

	t.Run("same group", func(t *testing.T) {
		a := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
		b := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &sameSecond}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
		require.True(t, sameAccountWithLoadGroup(a, b))
	})

	t.Run("different priority", func(t *testing.T) {
		a := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
		b := accountWithLoad{account: &Account{Priority: 2, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
		require.False(t, sameAccountWithLoadGroup(a, b))
	})

	t.Run("different load rate", func(t *testing.T) {
		a := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
		b := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 20}}
		require.False(t, sameAccountWithLoadGroup(a, b))
	})

	t.Run("different last used at", func(t *testing.T) {
		later := now.Add(1 * time.Second)
		a := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
		b := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &later}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
		require.False(t, sameAccountWithLoadGroup(a, b))
	})

	t.Run("both nil LastUsedAt", func(t *testing.T) {
		a := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: nil}, loadInfo: &AccountLoadInfo{LoadRate: 0}}
		b := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: nil}, loadInfo: &AccountLoadInfo{LoadRate: 0}}
		require.True(t, sameAccountWithLoadGroup(a, b))
	})
}

// ============ sameAccountGroup 测试 ============

func TestSameAccountGroup(t *testing.T) {
	now := time.Now()

	t.Run("same group", func(t *testing.T) {
		a := &Account{Priority: 1, LastUsedAt: nil}
		b := &Account{Priority: 1, LastUsedAt: nil}
		require.True(t, sameAccountGroup(a, b))
	})

	t.Run("different priority", func(t *testing.T) {
		a := &Account{Priority: 1, LastUsedAt: nil}
		b := &Account{Priority: 2, LastUsedAt: nil}
		require.False(t, sameAccountGroup(a, b))
	})

	t.Run("different LastUsedAt", func(t *testing.T) {
		later := now.Add(1 * time.Second)
		a := &Account{Priority: 1, LastUsedAt: &now}
		b := &Account{Priority: 1, LastUsedAt: &later}
		require.False(t, sameAccountGroup(a, b))
	})
}

// ============ sortAccountsByPriorityAndLastUsed 集成随机化测试 ============

func TestSortAccountsByPriorityAndLastUsed_WithShuffle(t *testing.T) {
	t.Run("same priority and nil LastUsedAt are shuffled", func(t *testing.T) {
		accounts := []*Account{
			{ID: 1, Priority: 1, LastUsedAt: nil},
			{ID: 2, Priority: 1, LastUsedAt: nil},
			{ID: 3, Priority: 1, LastUsedAt: nil},
		}

		seen := map[int64]bool{}
		for i := 0; i < 100; i++ {
			cpy := make([]*Account, len(accounts))
			copy(cpy, accounts)
			sortAccountsByPriorityAndLastUsed(cpy, false)
			seen[cpy[0].ID] = true
		}
		require.GreaterOrEqual(t, len(seen), 2, "identical sort keys should produce different orderings after shuffle")
	})

	t.Run("different priorities still sorted correctly", func(t *testing.T) {
		now := time.Now()
		accounts := []*Account{
			{ID: 3, Priority: 3, LastUsedAt: &now},
			{ID: 1, Priority: 1, LastUsedAt: &now},
			{ID: 2, Priority: 2, LastUsedAt: &now},
		}

		sortAccountsByPriorityAndLastUsed(accounts, false)
		require.Equal(t, int64(1), accounts[0].ID)
		require.Equal(t, int64(2), accounts[1].ID)
		require.Equal(t, int64(3), accounts[2].ID)
	})
}
