package domain_test

import (
	"fmt"
	"slices"
	"testing"
	"time"

	"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 TestNewCreateTribeSnapshotParams(t *testing.T) {
	t.Parallel()

	tribes := domain.Tribes{
		domaintest.NewTribe(t),
		domaintest.NewTribe(t),
		domaintest.NewTribe(t),
	}
	date := time.Now()

	res, err := domain.NewCreateTribeSnapshotParams(tribes, date)
	require.NoError(t, err)
	assert.Len(t, res, len(tribes))
	for i, tr := range tribes {
		idx := slices.IndexFunc(res, func(params domain.CreateTribeSnapshotParams) bool {
			return params.TribeID() == tr.ID() && params.ServerKey() == tr.ServerKey()
		})
		require.GreaterOrEqualf(t, idx, 0, "tribes[%d] not found", i)

		params := res[i]

		assert.Equalf(t, tr.ID(), params.TribeID(), "tribes[%d]", i)
		assert.Equalf(t, tr.ServerKey(), params.ServerKey(), "tribes[%d]", i)
		assert.Equalf(t, tr.NumMembers(), params.NumMembers(), "tribes[%d]", i)
		assert.Equalf(t, tr.NumVillages(), params.NumVillages(), "tribes[%d]", i)
		assert.Equalf(t, tr.Points(), params.Points(), "tribes[%d]", i)
		assert.Equalf(t, tr.AllPoints(), params.AllPoints(), "tribes[%d]", i)
		assert.Equalf(t, tr.Rank(), params.Rank(), "tribes[%d]", i)
		assert.Equalf(t, tr.OD(), params.OD(), "tribes[%d]", i)
		assert.InDeltaf(t, tr.Dominance(), params.Dominance(), 0.001, "tribes[%d]", i)
		assert.Equalf(t, date, params.Date(), "tribes[%d]", i)
	}
}

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

	type args struct {
		sorts [2]domain.TribeSnapshotSort
	}

	tests := []struct {
		name        string
		args        args
		expectedRes bool
	}{
		{
			name: "OK: id:ASC serverKey:ASC",
			args: args{
				sorts: [2]domain.TribeSnapshotSort{domain.TribeSnapshotSortIDASC, domain.TribeSnapshotSortServerKeyASC},
			},
			expectedRes: false,
		},
		{
			name: "OK: id:DESC serverKey:ASC",
			args: args{
				sorts: [2]domain.TribeSnapshotSort{domain.TribeSnapshotSortIDDESC, domain.TribeSnapshotSortServerKeyASC},
			},
			expectedRes: false,
		},
		{
			name: "OK: id:ASC id:ASC",
			args: args{
				sorts: [2]domain.TribeSnapshotSort{domain.TribeSnapshotSortIDASC, domain.TribeSnapshotSortIDASC},
			},
			expectedRes: true,
		},
		{
			name: "OK: id:ASC id:DESC",
			args: args{
				sorts: [2]domain.TribeSnapshotSort{domain.TribeSnapshotSortIDASC, domain.TribeSnapshotSortIDDESC},
			},
			expectedRes: true,
		},
		{
			name: "OK: date:ASC date:DESC",
			args: args{
				sorts: [2]domain.TribeSnapshotSort{domain.TribeSnapshotSortDateASC, domain.TribeSnapshotSortDateDESC},
			},
			expectedRes: true,
		},
		{
			name: "OK: serverKey:DESC serverKey:ASC",
			args: args{
				sorts: [2]domain.TribeSnapshotSort{domain.TribeSnapshotSortServerKeyDESC, domain.TribeSnapshotSortServerKeyASC},
			},
			expectedRes: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			assert.Equal(t, tt.expectedRes, tt.args.sorts[0].IsInConflict(tt.args.sorts[1]))
		})
	}
}

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

	type args struct {
		serverKeys []string
	}

	type test struct {
		name        string
		args        args
		expectedErr error
	}

	tests := []test{
		{
			name: "OK",
			args: args{
				serverKeys: []string{
					domaintest.RandServerKey(),
				},
			},
		},
	}

	for _, serverKeyTest := range newServerKeyValidationTests() {
		tests = append(tests, test{
			name: serverKeyTest.name,
			args: args{
				serverKeys: []string{serverKeyTest.key},
			},
			expectedErr: domain.SliceElementValidationError{
				Model: "ListTribeSnapshotsParams",
				Field: "serverKeys",
				Index: 0,
				Err:   serverKeyTest.expectedErr,
			},
		})
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			params := domain.NewListTribeSnapshotsParams()

			require.ErrorIs(t, params.SetServerKeys(tt.args.serverKeys), tt.expectedErr)
			if tt.expectedErr != nil {
				return
			}
			assert.Equal(t, tt.args.serverKeys, params.ServerKeys())
		})
	}
}

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

	type args struct {
		sort []domain.TribeSnapshotSort
	}

	tests := []struct {
		name        string
		args        args
		expectedErr error
	}{
		{
			name: "OK",
			args: args{
				sort: []domain.TribeSnapshotSort{
					domain.TribeSnapshotSortDateASC,
					domain.TribeSnapshotSortServerKeyASC,
				},
			},
		},
		{
			name: "ERR: len(sort) < 1",
			args: args{
				sort: nil,
			},
			expectedErr: domain.ValidationError{
				Model: "ListTribeSnapshotsParams",
				Field: "sort",
				Err: domain.LenOutOfRangeError{
					Min:     1,
					Max:     3,
					Current: 0,
				},
			},
		},
		{
			name: "ERR: len(sort) > 3",
			args: args{
				sort: []domain.TribeSnapshotSort{
					domain.TribeSnapshotSortDateASC,
					domain.TribeSnapshotSortServerKeyASC,
					domain.TribeSnapshotSortIDASC,
					domain.TribeSnapshotSortIDDESC,
				},
			},
			expectedErr: domain.ValidationError{
				Model: "ListTribeSnapshotsParams",
				Field: "sort",
				Err: domain.LenOutOfRangeError{
					Min:     1,
					Max:     3,
					Current: 4,
				},
			},
		},
		{
			name: "ERR: conflict",
			args: args{
				sort: []domain.TribeSnapshotSort{
					domain.TribeSnapshotSortIDASC,
					domain.TribeSnapshotSortIDDESC,
				},
			},
			expectedErr: domain.ValidationError{
				Model: "ListTribeSnapshotsParams",
				Field: "sort",
				Err: domain.SortConflictError{
					Sort: [2]string{domain.TribeSnapshotSortIDASC.String(), domain.TribeSnapshotSortIDDESC.String()},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			params := domain.NewListTribeSnapshotsParams()

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

func TestListTribeSnapshotsParams_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.TribeSnapshotListMaxLimit,
			},
		},
		{
			name: "ERR: limit < 1",
			args: args{
				limit: 0,
			},
			expectedErr: domain.ValidationError{
				Model: "ListTribeSnapshotsParams",
				Field: "limit",
				Err: domain.MinGreaterEqualError{
					Min:     1,
					Current: 0,
				},
			},
		},
		{
			name: fmt.Sprintf("ERR: limit > %d", domain.TribeSnapshotListMaxLimit),
			args: args{
				limit: domain.TribeSnapshotListMaxLimit + 1,
			},
			expectedErr: domain.ValidationError{
				Model: "ListTribeSnapshotsParams",
				Field: "limit",
				Err: domain.MaxLessEqualError{
					Max:     domain.TribeSnapshotListMaxLimit,
					Current: domain.TribeSnapshotListMaxLimit + 1,
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			params := domain.NewListTribeSnapshotsParams()

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

func TestListTribeSnapshotsParams_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: "ListTribeSnapshotsParams",
				Field: "offset",
				Err: domain.MinGreaterEqualError{
					Min:     0,
					Current: -1,
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			params := domain.NewListTribeSnapshotsParams()

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