public async Task Unmatched_PlayerIdentity_returns_new_player_and_identity()
        {
            using (var connection = _databaseFixture.ConnectionFactory.CreateDatabaseConnection())
            {
                connection.Open();
                using (var transaction = connection.BeginTransaction())
                {
                    var playerIdentity = new PlayerIdentity
                    {
                        PlayerIdentityName = $"New player {Guid.NewGuid()}",
                        Team = new Team {
                            TeamId = _databaseFixture.TestData.TeamWithFullDetails.TeamId
                        }
                    };
                    var playerRoute = $"/players/{playerIdentity.PlayerIdentityName.Kebaberize()}";
                    var copier      = new Mock <IStoolballEntityCopier>();
                    copier.Setup(x => x.CreateAuditableCopy(playerIdentity)).Returns(playerIdentity);
                    var playerNameFormatter = new Mock <IPlayerNameFormatter>();
                    playerNameFormatter.Setup(x => x.CapitaliseName(playerIdentity.PlayerIdentityName)).Returns(playerIdentity.PlayerIdentityName);
                    var routeGenerator = new Mock <IRouteGenerator>();
                    routeGenerator.Setup(x => x.GenerateUniqueRoute("/players", playerIdentity.PlayerIdentityName, NoiseWords.PlayerRoute, It.IsAny <Func <string, Task <int> > >())).Returns(Task.FromResult(playerRoute));

                    var repo = new SqlServerPlayerRepository(Mock.Of <IAuditRepository>(), Mock.Of <ILogger>(), routeGenerator.Object, copier.Object, playerNameFormatter.Object);

                    var result = await repo.CreateOrMatchPlayerIdentity(playerIdentity, Guid.NewGuid(), "Member name", transaction).ConfigureAwait(false);

                    Assert.NotNull(result);
                    copier.Verify(x => x.CreateAuditableCopy(playerIdentity), Times.Once);
                    playerNameFormatter.Verify(x => x.CapitaliseName(playerIdentity.PlayerIdentityName));
                    routeGenerator.Verify(x => x.GenerateUniqueRoute("/players", playerIdentity.PlayerIdentityName, NoiseWords.PlayerRoute, It.IsAny <Func <string, Task <int> > >()));

                    var identityResult = await transaction.Connection.QuerySingleAsync <PlayerIdentityResult>(
                        $"SELECT PlayerId, PlayerIdentityName, ComparableName, TeamId FROM {Tables.PlayerIdentity} WHERE PlayerIdentityName = @PlayerIdentityName",
                        new { playerIdentity.PlayerIdentityName },
                        transaction).ConfigureAwait(false);

                    Assert.NotNull(identityResult);
                    Assert.Equal(playerIdentity.PlayerIdentityName, identityResult.PlayerIdentityName);
                    Assert.Equal(playerIdentity.ComparableName(), identityResult.ComparableName);
                    Assert.Equal(playerIdentity.Team.TeamId, identityResult.TeamId);

                    var playerResult = await transaction.Connection.QuerySingleAsync <Player>(
                        $"SELECT PlayerRoute FROM {Tables.Player} WHERE PlayerId = @PlayerId",
                        new { identityResult.PlayerId },
                        transaction).ConfigureAwait(false);

                    Assert.NotNull(playerResult);
                    Assert.Equal(playerRoute, playerResult.PlayerRoute);

                    transaction.Rollback();
                }
            }
        }
        /// <summary>
        /// Finds an existing player identity or creates it if it is not found
        /// </summary>
        /// <returns>The <see cref="PlayerIdentity.PlayerIdentityId"/> of the created or matched player identity</returns>
        public async Task <PlayerIdentity> CreateOrMatchPlayerIdentity(PlayerIdentity playerIdentity, Guid memberKey, string memberName, IDbTransaction transaction)
        {
            if (playerIdentity is null)
            {
                throw new ArgumentNullException(nameof(playerIdentity));
            }

            if (playerIdentity.PlayerIdentityId.HasValue && playerIdentity.Player.PlayerId.HasValue)
            {
                return(playerIdentity);
            }

            if (string.IsNullOrWhiteSpace(playerIdentity.PlayerIdentityName))
            {
                throw new ArgumentException($"'{nameof(playerIdentity)}.PlayerIdentityName' cannot be null or whitespace", nameof(playerIdentity));
            }

            if (playerIdentity.Team?.TeamId == null)
            {
                throw new ArgumentException($"'{nameof(playerIdentity)}.Team.TeamId' cannot be null", nameof(playerIdentity));
            }

            if (string.IsNullOrWhiteSpace(memberName))
            {
                throw new ArgumentNullException(nameof(memberName));
            }

            if (transaction is null)
            {
                throw new ArgumentNullException(nameof(transaction));
            }

            var matchedPlayerIdentity = (await transaction.Connection.QueryAsync <PlayerIdentity, Player, PlayerIdentity>(
                                             $"SELECT PlayerIdentityId, PlayerIdentityName, PlayerId FROM {Tables.PlayerIdentity} WHERE ComparableName = @ComparableName AND TeamId = @TeamId",
                                             (pi, p) =>
            {
                pi.Player = p;
                return(pi);
            },
                                             new
            {
                ComparableName = playerIdentity.ComparableName(),
                playerIdentity.Team.TeamId
            },
                                             transaction,
                                             splitOn: "PlayerId").ConfigureAwait(false)).FirstOrDefault();

            if (matchedPlayerIdentity != null && matchedPlayerIdentity.PlayerIdentityId.HasValue && matchedPlayerIdentity.Player.PlayerId.HasValue)
            {
                matchedPlayerIdentity.Team = playerIdentity.Team;
                return(matchedPlayerIdentity);
            }

            var auditablePlayerIdentity = _copier.CreateAuditableCopy(playerIdentity);

            auditablePlayerIdentity.PlayerIdentityId   = Guid.NewGuid();
            auditablePlayerIdentity.PlayerIdentityName = _playerNameFormatter.CapitaliseName(auditablePlayerIdentity.PlayerIdentityName);

            var player = new Player {
                PlayerId = Guid.NewGuid()
            };

            player.PlayerIdentities.Add(auditablePlayerIdentity);

            player.PlayerRoute = await _routeGenerator.GenerateUniqueRoute($"/players", auditablePlayerIdentity.PlayerIdentityName, NoiseWords.PlayerRoute,
                                                                           async route => await transaction.Connection.ExecuteScalarAsync <int>($"SELECT COUNT(*) FROM {Tables.Player} WHERE PlayerRoute = @PlayerRoute", new { player.PlayerRoute }, transaction).ConfigureAwait(false)
                                                                           ).ConfigureAwait(false);

            await transaction.Connection.ExecuteAsync(
                $@"INSERT INTO {Tables.Player} 
                                               (PlayerId, PlayerRoute) 
                                               VALUES 
                                               (@PlayerId, @PlayerRoute)",
                new
            {
                player.PlayerId,
                player.PlayerRoute
            }, transaction).ConfigureAwait(false);

            await transaction.Connection.ExecuteAsync($@"INSERT INTO {Tables.PlayerIdentity} 
                                (PlayerIdentityId, PlayerId, PlayerIdentityName, ComparableName, TeamId) 
                                VALUES (@PlayerIdentityId, @PlayerId, @PlayerIdentityName, @ComparableName, @TeamId)",
                                                      new
            {
                auditablePlayerIdentity.PlayerIdentityId,
                player.PlayerId,
                auditablePlayerIdentity.PlayerIdentityName,
                ComparableName = auditablePlayerIdentity.ComparableName(),
                auditablePlayerIdentity.Team.TeamId
            }, transaction).ConfigureAwait(false);

            var serialisedPlayer = JsonConvert.SerializeObject(player);
            await _auditRepository.CreateAudit(new AuditRecord
            {
                Action        = AuditAction.Create,
                MemberKey     = memberKey,
                ActorName     = memberName,
                EntityUri     = player.EntityUri,
                State         = serialisedPlayer,
                RedactedState = serialisedPlayer,
                AuditDate     = DateTime.UtcNow
            }, transaction).ConfigureAwait(false);

            _logger.Info(GetType(), LoggingTemplates.Created, player, memberName, memberKey, GetType(), nameof(CreateOrMatchPlayerIdentity));

            player.PlayerIdentities.Clear();
            auditablePlayerIdentity.Player = player;
            return(auditablePlayerIdentity);
        }