package command

import (
	"context"
	"io"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.org/x/text/language"

	"github.com/zitadel/zitadel/internal/eventstore"
	"github.com/zitadel/zitadel/internal/i18n"
	"github.com/zitadel/zitadel/internal/repository/user"
)

var (
	SupportedLanguages   = []language.Tag{language.English, language.German}
	OnlyAllowedLanguages = []language.Tag{language.English}
	AllowedLanguage      = language.English
	DisallowedLanguage   = language.German
	UnsupportedLanguage  = language.Spanish
)

func TestMain(m *testing.M) {
	i18n.SupportLanguages(SupportedLanguages...)
	os.Exit(m.Run())
}

func TestCommands_asyncPush(t *testing.T) {
	// make sure the test terminates on deadlock
	background := context.Background()
	agg := user.NewAggregate("userID", "orgID")
	cmd := user.NewMachineSecretCheckFailedEvent(background, &agg.Aggregate)

	tests := []struct {
		name         string
		pushCtx      func() (context.Context, context.CancelFunc)
		eventstore   func(*testing.T) *eventstore.Eventstore
		closeCtx     func() (context.Context, context.CancelFunc)
		wantCloseErr bool
	}{
		{
			name: "push error",
			pushCtx: func() (context.Context, context.CancelFunc) {
				return context.WithCancel(background)
			},
			eventstore: expectEventstore(
				expectPushFailed(io.ErrClosedPipe, cmd),
			),
			closeCtx: func() (context.Context, context.CancelFunc) {
				return context.WithTimeout(background, time.Second)
			},
			wantCloseErr: false,
		},
		{
			name: "success",
			pushCtx: func() (context.Context, context.CancelFunc) {
				return context.WithCancel(background)
			},
			eventstore: expectEventstore(
				expectPushSlow(time.Second/10, cmd),
			),
			closeCtx: func() (context.Context, context.CancelFunc) {
				return context.WithTimeout(background, time.Second)
			},
			wantCloseErr: false,
		},
		{
			name: "success after push context cancels",
			pushCtx: func() (context.Context, context.CancelFunc) {
				ctx, cancel := context.WithCancel(background)
				cancel()
				return ctx, cancel
			},
			eventstore: expectEventstore(
				expectPushSlow(time.Second/10, cmd),
			),
			closeCtx: func() (context.Context, context.CancelFunc) {
				return context.WithTimeout(background, time.Second)
			},
			wantCloseErr: false,
		},
		{
			name: "success after push context timeout",
			pushCtx: func() (context.Context, context.CancelFunc) {
				return context.WithTimeout(background, time.Second/100)
			},
			eventstore: expectEventstore(
				expectPushSlow(time.Second/10, cmd),
			),
			closeCtx: func() (context.Context, context.CancelFunc) {
				return context.WithTimeout(background, time.Second)
			},
			wantCloseErr: false,
		},
		{
			name: "success after push context timeout",
			pushCtx: func() (context.Context, context.CancelFunc) {
				return context.WithTimeout(background, time.Second/100)
			},
			eventstore: expectEventstore(
				expectPushSlow(time.Second/10, cmd),
			),
			closeCtx: func() (context.Context, context.CancelFunc) {
				return context.WithTimeout(background, time.Second)
			},
			wantCloseErr: false,
		},
		{
			name: "close timeout error",
			pushCtx: func() (context.Context, context.CancelFunc) {
				return context.WithCancel(background)
			},
			eventstore: expectEventstore(
				expectPushSlow(time.Second/10, cmd),
			),
			closeCtx: func() (context.Context, context.CancelFunc) {
				return context.WithTimeout(background, time.Second/100)
			},
			wantCloseErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := &Commands{
				eventstore: tt.eventstore(t),
			}
			c.eventstore.PushTimeout = 10 * time.Second
			pushCtx, cancel := tt.pushCtx()
			c.asyncPush(pushCtx, cmd)
			cancel()

			closeCtx, cancel := tt.closeCtx()
			defer cancel()
			err := c.Close(closeCtx)
			if tt.wantCloseErr {
				assert.Error(t, err)
				return
			}
			require.NoError(t, err)
		})
	}
}