package adapter_test

import (
	"cmp"
	"context"
	"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 testVersionRepository(t *testing.T, newRepos func(t *testing.T) repositories) {
	t.Helper()

	ctx := context.Background()

	t.Run("List", func(t *testing.T) {
		t.Parallel()

		repos := newRepos(t)

		tests := []struct {
			name         string
			params       func(t *testing.T) domain.ListVersionsParams
			assertResult func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult)
			assertError  func(t *testing.T, err error)
		}{
			{
				name: "OK: default params",
				params: func(t *testing.T) domain.ListVersionsParams {
					t.Helper()
					return domain.NewListVersionsParams()
				},
				assertResult: func(t *testing.T, _ domain.ListVersionsParams, res domain.ListVersionsResult) {
					t.Helper()
					assert.NotEmpty(t, res.Versions())
					assert.True(t, slices.IsSortedFunc(res.Versions(), func(a, b domain.Version) int {
						return cmp.Compare(a.Code(), b.Code())
					}))
					assert.False(t, res.Self().IsZero())
					assert.True(t, res.Next().IsZero())
				},
				assertError: func(t *testing.T, err error) {
					t.Helper()
					require.NoError(t, err)
				},
			},
			{
				name: "OK: sort=[code DESC]",
				params: func(t *testing.T) domain.ListVersionsParams {
					t.Helper()
					params := domain.NewListVersionsParams()
					require.NoError(t, params.SetSort([]domain.VersionSort{domain.VersionSortCodeDESC}))
					return params
				},
				assertResult: func(t *testing.T, _ domain.ListVersionsParams, res domain.ListVersionsResult) {
					t.Helper()
					assert.NotEmpty(t, res.Versions())
					assert.True(t, slices.IsSortedFunc(res.Versions(), func(a, b domain.Version) int {
						return cmp.Compare(a.Code(), b.Code()) * -1
					}))
				},
				assertError: func(t *testing.T, err error) {
					t.Helper()
					require.NoError(t, err)
				},
			},
			{
				name: "OK: cursor sort=[code ASC]",
				params: func(t *testing.T) domain.ListVersionsParams {
					t.Helper()

					params := domain.NewListVersionsParams()
					require.NoError(t, params.SetSort([]domain.VersionSort{domain.VersionSortCodeASC}))

					res, err := repos.version.List(ctx, params)
					require.NoError(t, err)
					require.Greater(t, len(res.Versions()), 2)

					require.NoError(t, params.SetCursor(domaintest.NewVersionCursor(t, func(cfg *domaintest.VersionCursorConfig) {
						cfg.Code = res.Versions()[1].Code()
					})))

					return params
				},
				assertResult: func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult) {
					t.Helper()
					assert.NotEmpty(t, res.Versions())
					assert.True(t, slices.IsSortedFunc(res.Versions(), func(a, b domain.Version) int {
						return cmp.Compare(a.Code(), b.Code())
					}))
					for _, v := range res.Versions() {
						assert.GreaterOrEqual(t, v.Code(), params.Cursor().Code(), v.Code())
					}
				},
				assertError: func(t *testing.T, err error) {
					t.Helper()
					require.NoError(t, err)
				},
			},
			{
				name: "OK: cursor sort=[code DESC]",
				params: func(t *testing.T) domain.ListVersionsParams {
					t.Helper()

					params := domain.NewListVersionsParams()
					require.NoError(t, params.SetSort([]domain.VersionSort{domain.VersionSortCodeDESC}))

					res, err := repos.version.List(ctx, params)
					require.NoError(t, err)
					require.Greater(t, len(res.Versions()), 2)

					require.NoError(t, params.SetCursor(domaintest.NewVersionCursor(t, func(cfg *domaintest.VersionCursorConfig) {
						cfg.Code = res.Versions()[1].Code()
					})))

					return params
				},
				assertResult: func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult) {
					t.Helper()
					assert.NotEmpty(t, res.Versions())
					assert.True(t, slices.IsSortedFunc(res.Versions(), func(a, b domain.Version) int {
						return cmp.Compare(a.Code(), b.Code()) * -1
					}))
					for _, v := range res.Versions() {
						assert.LessOrEqual(t, v.Code(), params.Cursor().Code(), v.Code())
					}
				},
				assertError: func(t *testing.T, err error) {
					t.Helper()
					require.NoError(t, err)
				},
			},
			{
				name: "OK: codes limit=1",
				params: func(t *testing.T) domain.ListVersionsParams {
					t.Helper()

					params := domain.NewListVersionsParams()
					require.NoError(t, params.SetLimit(1))

					res, err := repos.version.List(ctx, params)
					require.NoError(t, err)
					require.NotEmpty(t, res)

					require.NoError(t, params.SetCodes([]string{res.Versions()[0].Code()}))

					return params
				},
				assertResult: func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult) {
					t.Helper()
					versions := res.Versions()
					codes := params.Codes()
					assert.Len(t, versions, len(codes))
					for _, v := range versions {
						assert.Contains(t, codes, v.Code())
					}
				},
				assertError: func(t *testing.T, err error) {
					t.Helper()
					require.NoError(t, err)
				},
			},
			{
				name: "OK: limit=5",
				params: func(t *testing.T) domain.ListVersionsParams {
					t.Helper()
					params := domain.NewListVersionsParams()
					require.NoError(t, params.SetLimit(5))
					return params
				},
				assertResult: func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult) {
					t.Helper()
					assert.Len(t, res.Versions(), params.Limit())
					assert.False(t, res.Next().IsZero())
				},
				assertError: func(t *testing.T, err error) {
					t.Helper()
					require.NoError(t, err)
				},
			},
		}

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

				params := tt.params(t)

				res, err := repos.version.List(ctx, params)
				tt.assertError(t, err)
				tt.assertResult(t, params, res)
			})
		}
	})
}
