package domain_test

import (
	"cmp"
	"fmt"
	"slices"
	"testing"

	"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
	"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestNewCreateTribeChangeParams(t *testing.T) {
	t.Parallel()

	validTribeChange := domaintest.NewTribeChange(t)

	type args struct {
		serverKey  string
		playerID   int
		oldTribeID int
		newTribeID int
	}

	type test struct {
		name        string
		args        args
		expectedErr error
	}

	tests := []test{
		{
			name: "OK",
			args: args{
				serverKey:  validTribeChange.ServerKey(),
				playerID:   validTribeChange.PlayerID(),
				oldTribeID: validTribeChange.OldTribeID(),
				newTribeID: validTribeChange.NewTribeID(),
			},
		},
		{
			name: "ERR: playerID < 1",
			args: args{
				serverKey:  validTribeChange.ServerKey(),
				playerID:   0,
				oldTribeID: validTribeChange.OldTribeID(),
				newTribeID: validTribeChange.NewTribeID(),
			},
			expectedErr: domain.ValidationError{
				Model: "CreateTribeChangeParams",
				Field: "playerID",
				Err: domain.MinGreaterEqualError{
					Min:     1,
					Current: 0,
				},
			},
		},
		{
			name: "ERR: oldTribeID < 0",
			args: args{
				serverKey:  validTribeChange.ServerKey(),
				playerID:   validTribeChange.PlayerID(),
				oldTribeID: -1,
				newTribeID: validTribeChange.NewTribeID(),
			},
			expectedErr: domain.ValidationError{
				Model: "CreateTribeChangeParams",
				Field: "oldTribeID",
				Err: domain.MinGreaterEqualError{
					Min:     0,
					Current: -1,
				},
			},
		},
		{
			name: "ERR: newTribeID < 0",
			args: args{
				serverKey:  validTribeChange.ServerKey(),
				playerID:   validTribeChange.PlayerID(),
				oldTribeID: validTribeChange.OldTribeID(),
				newTribeID: -1,
			},
			expectedErr: domain.ValidationError{
				Model: "CreateTribeChangeParams",
				Field: "newTribeID",
				Err: domain.MinGreaterEqualError{
					Min:     0,
					Current: -1,
				},
			},
		},
	}

	for _, serverKeyTest := range newServerKeyValidationTests() {
		tests = append(tests, test{
			name: serverKeyTest.name,
			args: args{
				serverKey:  serverKeyTest.key,
				playerID:   validTribeChange.PlayerID(),
				oldTribeID: validTribeChange.OldTribeID(),
				newTribeID: validTribeChange.NewTribeID(),
			},
			expectedErr: domain.ValidationError{
				Model: "CreateTribeChangeParams",
				Field: "serverKey",
				Err:   serverKeyTest.expectedErr,
			},
		})
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			res, err := domain.NewCreateTribeChangeParams(
				tt.args.serverKey,
				tt.args.playerID,
				tt.args.oldTribeID,
				tt.args.newTribeID,
			)
			require.ErrorIs(t, err, tt.expectedErr)
			if tt.expectedErr != nil {
				return
			}
			assert.Equal(t, tt.args.serverKey, res.ServerKey())
			assert.Equal(t, tt.args.playerID, res.PlayerID())
			assert.Equal(t, tt.args.oldTribeID, res.OldTribeID())
			assert.Equal(t, tt.args.newTribeID, res.NewTribeID())
		})
	}
}

func TestNewCreateTribeChangeParamsFromPlayers(t *testing.T) {
	t.Parallel()

	server := domaintest.NewServer(t)

	players := domain.BasePlayers{
		domaintest.NewBasePlayer(t),
		domaintest.NewBasePlayer(t),
		domaintest.NewBasePlayer(t),
		domaintest.NewBasePlayer(t, func(cfg *domaintest.BasePlayerConfig) {
			cfg.TribeID = 0
		}),
	}

	storedPlayers := domain.Players{
		// new tribe id
		domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
			cfg.ID = players[0].ID()
			cfg.ServerKey = server.Key()
		}),
		// the same tribe id
		domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
			cfg.ID = players[1].ID()
			cfg.ServerKey = server.Key()
			cfg.TribeID = players[1].TribeID()
		}),
		// server with random server key to verify that slice order matters
		domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
			cfg.ID = players[2].ID()
			cfg.ServerKey = domaintest.RandServerKey()
		}),
	}

	expectedParams := []struct {
		serverKey  string
		playerID   int
		oldTribeID int
		newTribeID int
	}{
		{
			serverKey:  server.Key(),
			playerID:   players[0].ID(),
			oldTribeID: storedPlayers[0].TribeID(),
			newTribeID: players[0].TribeID(),
		},
		{
			serverKey:  server.Key(),
			playerID:   players[2].ID(),
			oldTribeID: 0,
			newTribeID: players[2].TribeID(),
		},
	}

	slices.SortFunc(players, func(a, b domain.BasePlayer) int {
		return cmp.Compare(a.ID(), b.ID())
	})

	slices.SortFunc(storedPlayers, func(a, b domain.Player) int {
		if res := cmp.Compare(a.ServerKey(), b.ServerKey()); res != 0 {
			return res
		}
		return cmp.Compare(a.ID(), b.ID())
	})

	res, err := domain.NewCreateTribeChangeParamsFromPlayers(server.Key(), players, storedPlayers)
	require.NoError(t, err)
	assert.Len(t, res, len(expectedParams))
	for i, expected := range expectedParams {
		idx := slices.IndexFunc(res, func(params domain.CreateTribeChangeParams) bool {
			return params.PlayerID() == expected.playerID && params.ServerKey() == expected.serverKey
		})
		require.GreaterOrEqualf(t, idx, 0, "expectedParams[%d] not found", i)

		params := res[idx]

		assert.Equalf(t, expected.serverKey, params.ServerKey(), "expectedParams[%d]", i)
		assert.Equalf(t, expected.playerID, params.PlayerID(), "expectedParams[%d]", i)
		assert.Equalf(t, expected.newTribeID, params.NewTribeID(), "expectedParams[%d]", i)
		assert.Equalf(t, expected.oldTribeID, params.OldTribeID(), "expectedParams[%d]", i)
	}
}

func TestListTribeChangesParams_SetSort(t *testing.T) {
	t.Parallel()

	type args struct {
		sort []domain.TribeChangeSort
	}

	tests := []struct {
		name        string
		args        args
		expectedErr error
	}{
		{
			name: "OK",
			args: args{
				sort: []domain.TribeChangeSort{
					domain.TribeChangeSortCreatedAtASC,
					domain.TribeChangeSortServerKeyASC,
				},
			},
		},
		{
			name: "ERR: len(sort) < 1",
			args: args{
				sort: nil,
			},
			expectedErr: domain.ValidationError{
				Model: "ListTribeChangesParams",
				Field: "sort",
				Err: domain.LenOutOfRangeError{
					Min:     1,
					Max:     3,
					Current: 0,
				},
			},
		},
		{
			name: "ERR: len(sort) > 3",
			args: args{
				sort: []domain.TribeChangeSort{
					domain.TribeChangeSortCreatedAtASC,
					domain.TribeChangeSortServerKeyASC,
					domain.TribeChangeSortIDASC,
					domain.TribeChangeSortIDDESC,
				},
			},
			expectedErr: domain.ValidationError{
				Model: "ListTribeChangesParams",
				Field: "sort",
				Err: domain.LenOutOfRangeError{
					Min:     1,
					Max:     3,
					Current: 4,
				},
			},
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			params := domain.NewListTribeChangesParams()

			require.ErrorIs(t, params.SetSort(tt.args.sort), tt.expectedErr)
			if tt.expectedErr != nil {
				return
			}
			assert.Equal(t, tt.args.sort, params.Sort())
		})
	}
}

func TestListTribeChangesParams_SetLimit(t *testing.T) {
	t.Parallel()

	type args struct {
		limit int
	}

	tests := []struct {
		name        string
		args        args
		expectedErr error
	}{
		{
			name: "OK",
			args: args{
				limit: domain.TribeChangeListMaxLimit,
			},
		},
		{
			name: "ERR: limit < 1",
			args: args{
				limit: 0,
			},
			expectedErr: domain.ValidationError{
				Model: "ListTribeChangesParams",
				Field: "limit",
				Err: domain.MinGreaterEqualError{
					Min:     1,
					Current: 0,
				},
			},
		},
		{
			name: fmt.Sprintf("ERR: limit > %d", domain.TribeChangeListMaxLimit),
			args: args{
				limit: domain.TribeChangeListMaxLimit + 1,
			},
			expectedErr: domain.ValidationError{
				Model: "ListTribeChangesParams",
				Field: "limit",
				Err: domain.MaxLessEqualError{
					Max:     domain.TribeChangeListMaxLimit,
					Current: domain.TribeChangeListMaxLimit + 1,
				},
			},
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			params := domain.NewListTribeChangesParams()

			require.ErrorIs(t, params.SetLimit(tt.args.limit), tt.expectedErr)
			if tt.expectedErr != nil {
				return
			}
			assert.Equal(t, tt.args.limit, params.Limit())
		})
	}
}

func TestListTribeChangesParams_SetOffset(t *testing.T) {
	t.Parallel()

	type args struct {
		offset int
	}

	tests := []struct {
		name        string
		args        args
		expectedErr error
	}{
		{
			name: "OK",
			args: args{
				offset: 100,
			},
		},
		{
			name: "ERR: offset < 0",
			args: args{
				offset: -1,
			},
			expectedErr: domain.ValidationError{
				Model: "ListTribeChangesParams",
				Field: "offset",
				Err: domain.MinGreaterEqualError{
					Min:     0,
					Current: -1,
				},
			},
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			params := domain.NewListTribeChangesParams()

			require.ErrorIs(t, params.SetOffset(tt.args.offset), tt.expectedErr)
			if tt.expectedErr != nil {
				return
			}
			assert.Equal(t, tt.args.offset, params.Offset())
		})
	}
}
