public RavenDbSetupHostedService(RavenDbSetupOptions setupOptions,
                                  IServiceProvider serviceProvider,
                                  ILogger <RavenDbSetupHostedService> logger)
 {
     _setupOptions    = setupOptions ?? throw new ArgumentNullException(nameof(setupOptions));
     _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
     _logger          = logger ?? throw new ArgumentNullException(nameof(logger));
 }
        /// <summary>
        /// Initializes a RavenDb database with some simple default settings.
        /// </summary>
        /// <remarks>The RavenDb instance is also setup as a Singleton in the IoC framework.<br/><b> No data is setup here. Use the SetupRavenDb extension method on an IDocumentStore, to do that.</b></remarks>
        /// <param name="services">Collection of services to setup.</param>
        /// <param name="options">Options required to initialize the database.</param>
        /// <param name="setupOptions">Optional: RavenDb setup options, like seeding data.</param>
        /// <returns>The same collection of services.</returns>
        public static IServiceCollection AddSimpleRavenDb(this IServiceCollection services,
                                                          RavenDbOptions options,
                                                          RavenDbSetupOptions setupOptions = null)
        {
            if (services is null)
            {
                throw new ArgumentNullException(nameof(services));
            }

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

            const string missingRavenDbConfigurationText = "Missing RavenDb configuration setting: ";

            if (options.ServerUrls?.Any() == false)
            {
                throw new Exception($"{missingRavenDbConfigurationText}{nameof(options.ServerUrls)}");
            }

            if (string.IsNullOrWhiteSpace(options.DatabaseName))
            {
                throw new Exception($"{missingRavenDbConfigurationText}{nameof(options.DatabaseName)}");
            }

            var documentStore = new DocumentStore
            {
                Urls     = options.ServerUrls.ToArray(),
                Database = options.DatabaseName
            };

            if (!string.IsNullOrWhiteSpace(options.X509CertificateBase64))
            {
                // REF: https://ayende.com/blog/186881-A/x509-certificates-vs-api-keys-in-ravendb
                documentStore.Certificate = new X509Certificate2(Convert.FromBase64String(options.X509CertificateBase64));
            }

            documentStore.Initialize();

            services.AddSingleton <IDocumentStore>(documentStore);

            // Do we wish to setup our DB? We should really do this BEFORE we accept web requests (if a web host)
            // or before the main app (console app) starts.
            if (setupOptions != null)
            {
                services.AddSingleton(setupOptions);

                services.AddHostedService <RavenDbSetupHostedService>();
            }

            return(services);
        }
        /// <summary>
        /// Setup a RavenDb database, ready to be used with some seeded, fake data (if provided) and any indexes (if provided).
        /// </summary>
        /// <param name="documentStore">DocumentStore to check.</param>
        /// <param name="setupOptions">Optional: any custom setup options, like data to seed.</param>
        /// <param name="logger">Logger.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <remarks>Default Polly policy is 15 checks, every 2 seconds (if none was provided).</remarks>
        public static async Task SetupRavenDbAsync(this IDocumentStore documentStore,
                                                   RavenDbSetupOptions setupOptions,
                                                   ILogger logger,
                                                   CancellationToken cancellationToken)
        {
            if (documentStore == null)
            {
                throw new ArgumentNullException(nameof(documentStore));
            }

            if (logger == null)
            {
                throw new ArgumentNullException(nameof(logger));
            }

            logger.LogDebug($" - Checking if the database '{documentStore.Database}' exists and if any data exists...");

            DatabaseStatistics existingDatabaseStatistics = null;

            try
            {
                var checkRavenDbPolicy = setupOptions?.Policy ?? CheckRavenDbPolicyAsync(logger, cancellationToken);
                await checkRavenDbPolicy.ExecuteAsync(async cancellationToken =>
                {
                    existingDatabaseStatistics = await documentStore.Maintenance.SendAsync(new GetStatisticsOperation(), cancellationToken);
                }, cancellationToken);
            }
            catch (DatabaseDoesNotExistException)
            {
                existingDatabaseStatistics = null; // No statistics because there's no Db tenant.
            }
            catch (AuthorizationException)
            {
                // We failed to authenticate against the database. This _usually_ means that we
                // probably didn't have ADMIN rights against a db (so we can't do high level stuff)
                // but we could still do other stuff, like add fake data for seeding the DB.
                // SUMMARY: Db Tenant might exist, so lets assume that it does (safer bet).
                existingDatabaseStatistics = new DatabaseStatistics();
            }

            if (cancellationToken.IsCancellationRequested)
            {
                logger.LogInformation("Cancelling setting up RavenDb database ...");
                cancellationToken.ThrowIfCancellationRequested();
            }

            try
            {
                await SetupRavenDbPolicyAsync(logger, cancellationToken).ExecuteAsync(async cancellationToken =>
                {
                    await SetupDatabaseTenantAsync(documentStore,
                                                   existingDatabaseStatistics != null,
                                                   logger,
                                                   cancellationToken);

                    if (cancellationToken.IsCancellationRequested)
                    {
                        logger.LogInformation("Cancelling setting up RavenDb database ...");
                        cancellationToken.ThrowIfCancellationRequested();
                    }

                    var documentCount = existingDatabaseStatistics?.CountOfDocuments ?? 0;
                    if (documentCount > 0)
                    {
                        logger.LogDebug($" - Skipping seeding fake data because database has {documentCount:N0} documents already in it.");
                    }
                    else
                    {
                        await SeedCollectionsOfFakeDataAsync(documentStore,
                                                             setupOptions?.DocumentCollections,
                                                             logger,
                                                             cancellationToken);
                    }

                    if (cancellationToken.IsCancellationRequested)
                    {
                        logger.LogInformation("Cancelling setting up RavenDb database ...");
                        cancellationToken.ThrowIfCancellationRequested();
                    }

                    await SetupIndexesAsync(documentStore,
                                            setupOptions?.IndexAssembly,
                                            logger,
                                            cancellationToken);
                }, cancellationToken);
            }
            catch (Exception exception)
            {
                logger.LogError("Failed to setup RavenDb and all retries failed. Unable to continue. Application is now terminating. Error: {exception}", exception);
                throw;
            }
        }