package controller_test

import (
	"testing"

	"galaxy/game/internal/controller"
	"galaxy/game/internal/model/game"

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

func TestVotesByRace(t *testing.T) {
	c, _ := newCache()

	c.MustPlanet(R0_Planet_0_num).Size = 450.
	c.MustPlanet(R0_Planet_0_num).Population = 450.

	c.MustPlanet(R1_Planet_1_num).Size = 900.
	c.MustPlanet(R1_Planet_1_num).Population = 900.

	c.MustPlanet(R0_Planet_2_num).Size = 330.
	c.MustPlanet(R0_Planet_2_num).Population = 330.

	vbr := c.VotesByRace()
	assert.Len(t, vbr, 2)
	assert.Contains(t, vbr, Race_0_idx)
	assert.Equal(t, 0.78, vbr[Race_0_idx])
	assert.Contains(t, vbr, Race_1_idx)
	assert.Equal(t, 0.9, vbr[Race_1_idx])
}

func prepareRaces() ([]game.Race, func(u uuid.UUID) int) {
	races := make([]game.Race, 20)
	raceIndex := make(map[uuid.UUID]int)
	for i := range len(races) {
		races[i].ID = uuid.New()
		races[i].VoteFor = races[i].ID
		raceIndex[races[i].ID] = i
	}
	// 0 -> 1 -> 2 -> 3
	//           ^    |
	// 5 -> 6 -> 4 <--'
	races[0].VoteFor = races[1].ID
	races[1].VoteFor = races[2].ID
	races[2].VoteFor = races[3].ID
	races[3].VoteFor = races[4].ID
	races[4].VoteFor = races[2].ID
	races[5].VoteFor = races[6].ID
	races[6].VoteFor = races[4].ID
	// 7 -> 10 -> 11
	//      ^
	// 8 -> 9
	races[7].VoteFor = races[10].ID
	races[10].VoteFor = races[11].ID
	races[8].VoteFor = races[9].ID
	races[9].VoteFor = races[10].ID
	// 12 -> 13
	// 13 -> 12
	races[12].VoteFor = races[13].ID
	races[13].VoteFor = races[12].ID
	// 14 -> 15 -> 16
	//       ^     |
	//       17 <--'
	races[14].VoteFor = races[15].ID
	races[15].VoteFor = races[16].ID
	races[16].VoteFor = races[17].ID
	races[17].VoteFor = races[15].ID
	// 19 -> 13
	races[19].VoteFor = races[13].ID

	return races, func(u uuid.UUID) int { return raceIndex[u] }
}

func TestVotingGraph(t *testing.T) {
	races, raceIndex := prepareRaces()

	voteNodes := controller.VotingGraph(races, raceIndex)

	assert.Len(t, voteNodes, len(races))
	for i := range voteNodes {
		n := voteNodes[i]
		switch i {
		case 0:
			assert.Equal(t, voteNodes[1], n.Next)
		case 1:
			assert.Equal(t, voteNodes[2], n.Next)
		case 2:
			assert.Equal(t, voteNodes[3], n.Next)
		case 3:
			assert.Equal(t, voteNodes[4], n.Next)
		case 4:
			assert.Equal(t, voteNodes[2], n.Next)
		case 5:
			assert.Equal(t, voteNodes[6], n.Next)
		case 6:
			assert.Equal(t, voteNodes[4], n.Next)
		case 7:
			assert.Equal(t, voteNodes[10], n.Next)
		case 8:
			assert.Equal(t, voteNodes[9], n.Next)
		case 9:
			assert.Equal(t, voteNodes[10], n.Next)
		case 10:
			assert.Equal(t, voteNodes[11], n.Next)
		case 11:
			assert.Nil(t, n.Next)
		case 12:
			assert.Equal(t, voteNodes[13], n.Next)
		case 13:
			assert.Equal(t, voteNodes[12], n.Next)
		case 14:
			assert.Equal(t, voteNodes[15], n.Next)
		case 15:
			assert.Equal(t, voteNodes[16], n.Next)
		case 16:
			assert.Equal(t, voteNodes[17], n.Next)
		case 17:
			assert.Equal(t, voteNodes[15], n.Next)
		case 18:
			assert.Nil(t, n.Next)
		case 19:
			assert.Equal(t, voteNodes[13], n.Next)
		}
	}
}

func TestVotingChains(t *testing.T) {
	races, raceIndex := prepareRaces()
	nodes := controller.VotingGraph(races, raceIndex)

	vc := controller.VotingChains(nodes)

	assert.Len(t, vc, 7)
	for i := range vc {
		n := vc[i]
		switch i {
		case 0:
			assert.Len(t, n, 3)
			assert.Equal(t, 2, n[0].ID)
			assert.Equal(t, 3, n[1].ID)
			assert.Equal(t, 4, n[2].ID)
			assert.True(t, n[0].Ally)
			assert.True(t, n[1].Ally)
			assert.True(t, n[2].Ally)
		case 1:
			assert.Len(t, n, 2)
			assert.Equal(t, 0, n[0].ID)
			assert.Equal(t, 1, n[1].ID)
			assert.False(t, n[0].Ally)
			assert.False(t, n[1].Ally)
		case 2:
			assert.Len(t, n, 2)
			assert.Equal(t, 5, n[0].ID)
			assert.Equal(t, 6, n[1].ID)
			assert.False(t, n[0].Ally)
			assert.False(t, n[1].Ally)
		case 3:
			assert.Len(t, n, 3)
			assert.Equal(t, 7, n[0].ID)
			assert.Equal(t, 10, n[1].ID)
			assert.Equal(t, 11, n[2].ID)
			assert.False(t, n[0].Ally)
			assert.False(t, n[1].Ally)
			assert.False(t, n[2].Ally)
		case 4:
			assert.Len(t, n, 4)
			assert.Equal(t, 8, n[0].ID)
			assert.Equal(t, 9, n[1].ID)
			assert.Equal(t, 10, n[2].ID)
			assert.Equal(t, 11, n[3].ID)
			assert.False(t, n[0].Ally)
			assert.False(t, n[1].Ally)
			assert.False(t, n[2].Ally)
			assert.False(t, n[3].Ally)
		case 5:
			assert.Len(t, n, 2)
			assert.Equal(t, 12, n[0].ID)
			assert.Equal(t, 13, n[1].ID)
			assert.True(t, n[0].Ally)
			assert.True(t, n[1].Ally)
		case 6:
			assert.Len(t, n, 3)
			assert.Equal(t, 15, n[0].ID)
			assert.Equal(t, 16, n[1].ID)
			assert.Equal(t, 17, n[2].ID)
			assert.True(t, n[0].Ally)
			assert.True(t, n[1].Ally)
			assert.True(t, n[2].Ally)
		}
	}
}

func TestGroupVotes(t *testing.T) {
	races, raceIndex := prepareRaces()
	raceVotes := make(map[int]float64)
	// [1] = 0.24
	raceVotes[0] = 0.11
	raceVotes[1] = 0.13

	// [2,3,4] = 0.69
	raceVotes[2] = 0.22
	raceVotes[3] = 0.23
	raceVotes[4] = 0.24

	// [6] = 0.71
	raceVotes[5] = 0.35
	raceVotes[6] = 0.36

	// [11] = 0.843
	raceVotes[7] = 0.41
	raceVotes[9] = 0.42
	raceVotes[10] = 0.013

	// [12,13] = 0.52
	raceVotes[12] = 0.52
	raceVotes[13] = 0.

	// [14] = 1.04
	raceVotes[14] = 1.04

	// [15,16,17] = 2.49
	raceVotes[15] = 1.15
	raceVotes[16] = 0.16
	raceVotes[17] = 1.18

	// [18] = 3.18
	raceVotes[18] = 3.18

	// [19] = 0.019
	raceVotes[19] = 0.019

	calc := controller.GroupVotes(raceVotes, controller.VotingGraph(races, raceIndex))

	assert.Len(t, calc, 9)
	for i := range calc {
		vg := calc[i]
		switch i {
		case 0:
			assert.ElementsMatch(t, []int{2, 3, 4}, vg.RaceIndex)
			assert.Equal(t, 0.69, vg.Sum)
		case 4:
			assert.ElementsMatch(t, []int{12, 13}, vg.RaceIndex)
			assert.Equal(t, 0.52, vg.Sum)
		case 5:
			assert.ElementsMatch(t, []int{15, 16, 17}, vg.RaceIndex)
			assert.InDelta(t, 2.49, vg.Sum, 0.001)
		default:
			assert.Len(t, vg.RaceIndex, 1)
			switch ri := vg.RaceIndex[0]; ri {
			case 1:
				assert.Equal(t, 0.24, vg.Sum)
			case 6:
				assert.Equal(t, 0.71, vg.Sum)
			case 11:
				assert.Equal(t, 0.843, vg.Sum)
			case 14:
				assert.Equal(t, 1.04, vg.Sum)
			case 18:
				assert.Equal(t, 3.18, vg.Sum)
			case 19:
				assert.Equal(t, 0.019, vg.Sum)
			default:
				assert.Failf(t, "unexpected group", "id=%v sum=%f", vg.RaceIndex, vg.Sum)
			}
		}
	}
}

func TestVotingWinners(t *testing.T) {
	gameVotes := 100.0
	var vg []*controller.VoteGroup
	var winners []int

	vg = []*controller.VoteGroup{
		{Sum: 4.0, RaceIndex: []int{0}},
		{Sum: 66.65, RaceIndex: []int{1, 2}},
		{Sum: 5.0, RaceIndex: []int{3}},
		{Sum: 25.0, RaceIndex: []int{4, 5, 6}},
	}
	winners = controller.VotingWinners(vg, gameVotes)
	assert.Len(t, winners, 0)

	vg = []*controller.VoteGroup{
		{Sum: 4.0, RaceIndex: []int{0}},
		{Sum: 66.666666666666666, RaceIndex: []int{1, 2}},
		{Sum: 5.0, RaceIndex: []int{3}},
		{Sum: 22.0, RaceIndex: []int{4, 5, 6}},
	}
	winners = controller.VotingWinners(vg, gameVotes)
	assert.ElementsMatch(t, winners, []int{1, 2})

	vg = []*controller.VoteGroup{
		{Sum: 4.0, RaceIndex: []int{0}},
		{Sum: 3.33, RaceIndex: []int{1, 2}},
		{Sum: 66.67, RaceIndex: []int{3}},
		{Sum: 25.0, RaceIndex: []int{4, 5, 6}},
	}
	winners = controller.VotingWinners(vg, gameVotes)
	assert.ElementsMatch(t, winners, []int{3})
}
