package app

import (
	"errors"
	"testing"

	"github.com/go-sql-driver/mysql"
	"github.com/stretchr/testify/require"
)

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

	generated := []string{"collision", "created"}
	generateCalls := 0
	inserted := make([]string, 0, len(generated))

	memberID, err := insertGeneratedMember(func() (string, error) {
		id := generated[generateCalls]
		generateCalls++
		return id, nil
	}, func(memberID string) error {
		inserted = append(inserted, memberID)
		if memberID == "collision" {
			return &mysql.MySQLError{Number: 1062, Message: "Duplicate entry 'collision' for key 'PRIMARY'"}
		}
		return nil
	}, "generate member id", "insert member")

	require.NoError(t, err)
	require.Equal(t, "created", memberID)
	require.Equal(t, []string{"collision", "created"}, inserted)
	require.Equal(t, 2, generateCalls)
}

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

	insertCalls := 0
	_, err := insertGeneratedMember(func() (string, error) {
		return "member", nil
	}, func(memberID string) error {
		insertCalls++
		return &mysql.MySQLError{Number: 1062, Message: "Duplicate entry 'wsp-user' for key 'members_workspace_human_user_unique'"}
	}, "generate member id", "insert member")

	require.EqualError(t, err, "insert member: Error 1062: Duplicate entry 'wsp-user' for key 'members_workspace_human_user_unique'")
	require.Equal(t, 1, insertCalls)
}

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

	insertCalls := 0
	_, err := insertGeneratedMember(func() (string, error) {
		return "collision", nil
	}, func(memberID string) error {
		insertCalls++
		return &mysql.MySQLError{Number: 1062, Message: "Duplicate entry 'collision' for key 'PRIMARY'"}
	}, "generate member id", "insert member")

	require.EqualError(t, err, "insert member after 4 generated id collisions: Error 1062: Duplicate entry 'collision' for key 'PRIMARY'")
	require.Equal(t, generatedMemberInsertAttempts, insertCalls)
}

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

	generateErr := errors.New("rng failed")
	_, err := insertGeneratedMember(func() (string, error) {
		return "", generateErr
	}, func(memberID string) error {
		t.Fatalf("insert called for memberID %q", memberID)
		return nil
	}, "generate member id", "insert member")

	require.ErrorIs(t, err, generateErr)
	require.EqualError(t, err, "generate member id: rng failed")
}
