package domain_test

import (
	"fmt"
	"math"
	"slices"
	"testing"

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

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

	open := domain.BaseServers{
		domaintest.NewBaseServer(t),
		domaintest.NewBaseServer(t),
		domaintest.NewBaseServer(t),
	}

	servers := domain.Servers{
		domaintest.NewServer(t, func(cfg *domaintest.ServerConfig) {
			cfg.Key = open[0].Key()
		}),
		domaintest.NewServer(t, func(cfg *domaintest.ServerConfig) {
			cfg.Key = open[2].Key()
		}),
		domaintest.NewServer(t),
		domaintest.NewServer(t, func(cfg *domaintest.ServerConfig) {
			cfg.Open = false
		}),
	}

	res, err := servers.Close(open)
	require.NoError(t, err)
	assert.NotEmpty(t, res)
	for _, s := range servers {
		expected := domaintest.NewBaseServer(t, func(cfg *domaintest.BaseServerConfig) {
			cfg.Key = s.Key()
			cfg.URL = s.URL()
			cfg.Open = false
		})

		if !s.Open() || slices.ContainsFunc(open, func(server domain.BaseServer) bool {
			return server.Key() == s.Key()
		}) {
			assert.NotContains(t, res, expected)
			continue
		}

		assert.Contains(t, res, expected)
	}
}

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

	validVersion := domaintest.NewVersion(t)
	validBaseServer := domaintest.NewBaseServer(t)

	type args struct {
		servers     domain.BaseServers
		versionCode string
	}

	type test struct {
		name        string
		args        args
		expectedErr error
	}

	tests := []test{
		{
			name: "OK",
			args: args{
				versionCode: validVersion.Code(),
				servers: domain.BaseServers{
					validBaseServer,
				},
			},
		},
	}

	for _, versionCodeTest := range newVersionCodeValidationTests() {
		tests = append(tests, test{
			name: versionCodeTest.name,
			args: args{
				versionCode: versionCodeTest.code,
				servers: domain.BaseServers{
					validBaseServer,
				},
			},
			expectedErr: domain.ValidationError{
				Model: "CreateServerParams",
				Field: "versionCode",
				Err:   versionCodeTest.expectedErr,
			},
		})
	}

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

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

			res, err := domain.NewCreateServerParams(tt.args.servers, tt.args.versionCode)
			require.ErrorIs(t, err, tt.expectedErr)
			if tt.expectedErr != nil {
				return
			}
			require.Len(t, res, len(tt.args.servers))
			for i, b := range tt.args.servers {
				assert.Equal(t, b, res[i].Base())
				assert.Equal(t, tt.args.versionCode, res[i].VersionCode())
			}
		})
	}
}

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

	type args struct {
		numTribes domain.NullInt
	}

	tests := []struct {
		name        string
		args        args
		expectedErr error
	}{
		{
			name: "OK",
			args: args{
				numTribes: domain.NullInt{
					Value: gofakeit.IntRange(0, math.MaxInt),
					Valid: true,
				},
			},
		},
		{
			name: "OK: null value",
			args: args{
				numTribes: domain.NullInt{
					Valid: false,
				},
			},
		},
		{
			name: "ERR: numTribes < 0",
			args: args{
				numTribes: domain.NullInt{
					Value: -1,
					Valid: true,
				},
			},
			expectedErr: domain.ValidationError{
				Model: "UpdateServerParams",
				Field: "numTribes",
				Err: domain.MinGreaterEqualError{
					Min:     0,
					Current: -1,
				},
			},
		},
	}

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

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

			var params domain.UpdateServerParams

			require.ErrorIs(t, params.SetNumTribes(tt.args.numTribes), tt.expectedErr)
			if tt.expectedErr != nil {
				return
			}
			assert.Equal(t, tt.args.numTribes, params.NumTribes())
		})
	}
}

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

	type args struct {
		sort []domain.ServerSort
	}

	tests := []struct {
		name        string
		args        args
		expectedErr error
	}{
		{
			name: "OK",
			args: args{
				sort: []domain.ServerSort{
					domain.ServerSortKeyASC,
				},
			},
		},
		{
			name: "ERR: len(sort) < 1",
			args: args{
				sort: nil,
			},
			expectedErr: domain.ValidationError{
				Model: "ListServersParams",
				Field: "sort",
				Err: domain.LenOutOfRangeError{
					Min:     1,
					Max:     2,
					Current: 0,
				},
			},
		},
		{
			name: "ERR: len(sort) > 2",
			args: args{
				sort: []domain.ServerSort{
					domain.ServerSortKeyASC,
					domain.ServerSortKeyDESC,
					domain.ServerSortOpenASC,
				},
			},
			expectedErr: domain.ValidationError{
				Model: "ListServersParams",
				Field: "sort",
				Err: domain.LenOutOfRangeError{
					Min:     1,
					Max:     2,
					Current: 3,
				},
			},
		},
	}

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

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

			params := domain.NewListServersParams()

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

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

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

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

			params := domain.NewListServersParams()

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

func TestListServersParams_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: "ListServersParams",
				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.NewListServersParams()

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

type serverKeyValidationTest struct {
	name        string
	key         string
	expectedErr error
}

func newServerKeyValidationTests() []serverKeyValidationTest {
	return []serverKeyValidationTest{
		{
			name: "ERR: server key length < 1",
			expectedErr: domain.LenOutOfRangeError{
				Min: 1,
				Max: 10,
			},
		},
		{
			name: "ERR: server key length > 10",
			key:  "keykeykeyke",
			expectedErr: domain.LenOutOfRangeError{
				Min:     1,
				Max:     10,
				Current: len("keykeykeyke"),
			},
		},
	}
}
