package domain_test

import (
	"fmt"
	"testing"

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

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

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

	tests := []struct {
		name        string
		args        args
		expectedRes bool
	}{
		{
			name: "OK: code:ASC code:ASC",
			args: args{
				sorts: [2]domain.VersionSort{domain.VersionSortCodeASC, domain.VersionSortCodeASC},
			},
			expectedRes: true,
		},
		{
			name: "OK: code:ASC code:DESC",
			args: args{
				sorts: [2]domain.VersionSort{domain.VersionSortCodeASC, domain.VersionSortCodeDESC},
			},
			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 TestNewVersionCursor(t *testing.T) {
	t.Parallel()

	validVersionCursor := domaintest.NewVersionCursor(t)

	type args struct {
		code string
	}

	type test struct {
		name        string
		args        args
		expectedErr error
	}

	tests := []test{
		{
			name: "OK",
			args: args{
				code: validVersionCursor.Code(),
			},
			expectedErr: nil,
		},
	}

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

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

			vc, err := domain.NewVersionCursor(tt.args.code)
			require.ErrorIs(t, err, tt.expectedErr)
			if tt.expectedErr != nil {
				return
			}
			assert.Equal(t, tt.args.code, vc.Code())
			assert.NotEmpty(t, vc.Encode())
		})
	}
}

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

	type args struct {
		codes []string
	}

	type test struct {
		name        string
		args        args
		expectedErr error
	}

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

	for _, versionCodeTest := range newVersionCodeValidationTests() {
		tests = append(tests, test{
			name: versionCodeTest.name,
			args: args{
				codes: []string{versionCodeTest.code},
			},
			expectedErr: domain.SliceElementValidationError{
				Model: "ListVersionsParams",
				Field: "codes",
				Index: 0,
				Err:   versionCodeTest.expectedErr,
			},
		})
	}

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

			params := domain.NewListVersionsParams()

			require.ErrorIs(t, params.SetCodes(tt.args.codes), tt.expectedErr)
			if tt.expectedErr != nil {
				return
			}
			assert.Equal(t, tt.args.codes, params.Codes())
		})
	}
}

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

	type args struct {
		sort []domain.VersionSort
	}

	tests := []struct {
		name        string
		args        args
		expectedErr error
	}{
		{
			name: "OK",
			args: args{
				sort: []domain.VersionSort{
					domain.VersionSortCodeASC,
				},
			},
		},
		{
			name: "OK: empty slice",
			args: args{
				sort: nil,
			},
		},
		{
			name: "ERR: len(sort) > 1",
			args: args{
				sort: []domain.VersionSort{
					domain.VersionSortCodeASC,
					domain.VersionSortCodeDESC,
				},
			},
			expectedErr: domain.ValidationError{
				Model: "ListVersionsParams",
				Field: "sort",
				Err: domain.LenOutOfRangeError{
					Min:     0,
					Max:     1,
					Current: 2,
				},
			},
		},
	}

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

			params := domain.NewListVersionsParams()

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

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

	validCursor := domaintest.NewVersionCursor(t)

	type args struct {
		cursor string
	}

	tests := []struct {
		name           string
		args           args
		expectedCursor domain.VersionCursor
		expectedErr    error
	}{
		{
			name: "OK",
			args: args{
				cursor: validCursor.Encode(),
			},
			expectedCursor: validCursor,
		},
		{
			name: "ERR: len(cursor) < 1",
			args: args{
				cursor: "",
			},
			expectedErr: domain.ValidationError{
				Model: "ListVersionsParams",
				Field: "cursor",
				Err: domain.LenOutOfRangeError{
					Min:     1,
					Max:     1000,
					Current: 0,
				},
			},
		},
		{
			name: "ERR: len(cursor) > 1000",
			args: args{
				cursor: gofakeit.LetterN(1001),
			},
			expectedErr: domain.ValidationError{
				Model: "ListVersionsParams",
				Field: "cursor",
				Err: domain.LenOutOfRangeError{
					Min:     1,
					Max:     1000,
					Current: 1001,
				},
			},
		},
		{
			name: "ERR: malformed base64",
			args: args{
				cursor: "112345",
			},
			expectedErr: domain.ValidationError{
				Model: "ListVersionsParams",
				Field: "cursor",
				Err:   domain.ErrInvalidCursor,
			},
		},
	}

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

			params := domain.NewListVersionsParams()

			require.ErrorIs(t, params.SetEncodedCursor(tt.args.cursor), tt.expectedErr)
			if tt.expectedErr != nil {
				return
			}
			assert.Equal(t, tt.expectedCursor.Code(), params.Cursor().Code())
			assert.Equal(t, tt.args.cursor, params.Cursor().Encode())
		})
	}
}

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

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

			params := domain.NewListVersionsParams()

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

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

	versions := domain.Versions{
		domaintest.NewVersion(t),
		domaintest.NewVersion(t),
		domaintest.NewVersion(t),
	}
	next := domaintest.NewVersion(t)

	t.Run("OK: with next", func(t *testing.T) {
		t.Parallel()

		res, err := domain.NewListVersionsResult(versions, next)
		require.NoError(t, err)
		assert.Equal(t, versions, res.Versions())
		assert.Equal(t, versions[0].Code(), res.Self().Code())
		assert.Equal(t, next.Code(), res.Next().Code())
	})

	t.Run("OK: without next", func(t *testing.T) {
		t.Parallel()

		res, err := domain.NewListVersionsResult(versions, domain.Version{})
		require.NoError(t, err)
		assert.Equal(t, versions, res.Versions())
		assert.Equal(t, versions[0].Code(), res.Self().Code())
		assert.True(t, res.Next().IsZero())
	})

	t.Run("OK: 0 versions", func(t *testing.T) {
		t.Parallel()

		res, err := domain.NewListVersionsResult(nil, domain.Version{})
		require.NoError(t, err)
		assert.Zero(t, res.Versions())
		assert.True(t, res.Self().IsZero())
		assert.True(t, res.Next().IsZero())
	})
}

type versionCodeValidationTest struct {
	name        string
	code        string
	expectedErr error
}

func newVersionCodeValidationTests() []versionCodeValidationTest {
	tooShort := gofakeit.LetterN(1)
	tooLong := gofakeit.LetterN(3)
	return []versionCodeValidationTest{
		{
			name: "ERR: version code length < 2",
			code: tooShort,
			expectedErr: domain.LenOutOfRangeError{
				Min:     2,
				Max:     2,
				Current: len(tooShort),
			},
		},
		{
			name: "ERR: version code length > 2",
			code: tooLong,
			expectedErr: domain.LenOutOfRangeError{
				Min:     2,
				Max:     2,
				Current: len(tooLong),
			},
		},
	}
}
