using System.IO;
using System.Reflection;
using System.Linq;
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.OpenApi.Models;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.SwaggerGen;

using Sockeye.Models;
using Sockeye.Util;
using Sockeye.Generator;
using Sockeye.Biz;
using NLog.Extensions.Logging;
using StackExchange.Profiling;


namespace Sockeye
{
    public class Startup
    {
        /////////////////////////////////////////////////////////////
        //
        public Startup(Microsoft.AspNetCore.Hosting.IWebHostEnvironment hostingEnvironment)
        {
            var nlogLoggerProvider = new NLogLoggerProvider();
            _newLog = nlogLoggerProvider.CreateLogger("SERVER");
            _hostingEnvironment = hostingEnvironment;
            Sockeye.Util.ApplicationLogging.LoggerProvider = nlogLoggerProvider;
            ServerBootConfig.SOCKEYE_CONTENT_ROOT_PATH = hostingEnvironment.ContentRootPath;

        }

        private readonly ILogger _newLog;
        private string _connectionString = "";
        private readonly Microsoft.AspNetCore.Hosting.IWebHostEnvironment _hostingEnvironment;



        ////////////////////////////////////////////////////////////
        // 
        //
        public void ConfigureServices(IServiceCollection services)
        {
            _newLog.LogDebug("Initializing services...");

            _newLog.LogDebug("Health");
            services.AddHealthChecks().AddDbContextCheck<AyContext>(); ;

            _newLog.LogDebug("Profiler");
            //https://dotnetthoughts.net/using-miniprofiler-in-aspnetcore-webapi/
            services.AddMemoryCache();
            services.AddMiniProfiler(options =>
            {
                options.RouteBasePath = "/profiler";
                //in testing only ignorepaths was reliable and worked and docs say it prevents any profiling at all
                options.IgnorePath("/auth").IgnorePath("/user").IgnorePath("/docs").IgnorePath("/cust").IgnorePath("/notify/hello").IgnorePath("/notify/new-count");
                options.ResultsAuthorize = request =>
                {
                    if (request.HttpContext.Items["AY_PROFILER_ALLOWED"] != null)
                        return true;
                    return false;
                };

            }).AddEntityFramework();


            //Server state service for shutting people out of api
            _newLog.LogDebug("ServerState service");
            services.AddSingleton(new Sockeye.Api.ControllerHelpers.ApiServerState());


            _newLog.LogDebug("Mail service");
            services.AddSingleton<IMailer, Mailer>();

            //Init controllers
            _newLog.LogDebug("Controllers");
            var MvcBuilder = services.AddControllers(config =>
                          {
                              // config.Filters.Add(new Sockeye.Api.ControllerHelpers.ApiCustomExceptionFilter(Sockeye.Util.ApplicationLogging.LoggerFactory));
                              config.Filters.Add(new Sockeye.Api.ControllerHelpers.ApiCustomExceptionFilter(_newLog));
                          });




            //Prevent default model binding automatic 400 page so we can consistently show *our* error to our specs
            //https://docs.microsoft.com/en-us/aspnet/core/web-api/index?view=aspnetcore-3.1#automatic-http-400-responses
            MvcBuilder.ConfigureApiBehaviorOptions(options =>
                    {
                        options.SuppressModelStateInvalidFilter = true;
                    });

            _newLog.LogDebug("JSON");

            MvcBuilder.AddNewtonsoftJson(options =>
                {
                    options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None;
                    options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;

                });


            //HTTP CLIENT FACTORY USED BY LICENSE.CS
            //https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1
            _newLog.LogDebug("HTTPClientFactory");
            services.AddHttpClient();





            //TODO: ENsure report files here

            #region DATABASE


            _connectionString = ServerBootConfig.SOCKEYE_DB_CONNECTION;

            //Check DB server exists and can be connected to
            _newLog.LogDebug("Testing database server connection...");

            //parse the connection string properly
            DbUtil.ParseConnectionString(_newLog, _connectionString);

            //Probe for database server
            //Will retry every 5 seconds for up to 5 minutes before bailing
            if (!DbUtil.DatabaseServerExists(_newLog, "Waiting for db server "))
            {
                var err = $"E1000 - Sockeye can't connect to the database server after trying for 5 minutes (connection string is:\"{DbUtil.DisplayableConnectionString}\")";
                _newLog.LogCritical(err);
                throw new System.ApplicationException(err);
            }


            //We have db available
            ServerGlobalOpsSettingsCache.DBAVAILABLE = true;

            _newLog.LogInformation("Connected to database server - {0}", DbUtil.DisplayableConnectionString);



            //ensure database is ready and present
            DbUtil.EnsureDatabaseExists(_newLog);

            bool LOG_SENSITIVE_DATA = false;

#if (DEBUG)
            LOG_SENSITIVE_DATA = false;//############################################################################

#endif

            _newLog.LogDebug("EF Core");


            //change to resolve error:
            //2020-12-28 09:20:14.3545|WARN|Microsoft.EntityFrameworkCore.Infrastructure|'AddEntityFramework*' was called on the service provider, but 'UseInternalServiceProvider' wasn't called in the DbContext options configuration. Consider removing the 'AddEntityFramework*' call, as in most cases it's not needed and may cause conflicts with other products and services registered in the same service provider.
            //https://stackoverflow.com/questions/62917136/addentityframework-was-called-on-the-service-provider-but-useinternalservic
            // services.AddEntityFrameworkNpgsql().AddDbContext<AyContext>(
            //     options => options.UseNpgsql(_connectionString
            //     //,opt => opt.EnableRetryOnFailure()//REMOVED THIS BECAUSE IT WAS INTEFERING WITH TRANSACTIONS BUT THEN DIDN'T USE THE TRANSACTION BUT IT SEEMS FASTER WITHOUT IT AS WELL SO...??
            //     )//http://www.npgsql.org/efcore/misc.html?q=execution%20strategy#execution-strategy
            //                 .ConfigureWarnings(warnings => //https://livebook.manning.com/#!/book/entity-framework-core-in-action/chapter-12/v-10/85
            //                         warnings.Throw( //Throw an exception on client eval, not necessarily an error but a smell
            //                                         // Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.QueryClientEvaluationWarning
            //                         ))
            //                 .EnableSensitiveDataLogging(LOG_SENSITIVE_DATA)
            //     );

            services.AddDbContext<AyContext>(options => options.UseNpgsql(_connectionString).ConfigureWarnings(warnings => warnings.Throw()).EnableSensitiveDataLogging(LOG_SENSITIVE_DATA));

            #endregion


            // Add service and create Policy with options
            _newLog.LogDebug("CORS");
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader().SetPreflightMaxAge(TimeSpan.FromSeconds(600))
                    );
            });




            #region Swagger

            services
                 .AddApiVersioning(options =>
                 {
                     options.AssumeDefaultVersionWhenUnspecified = true;
                     options.DefaultApiVersion = Microsoft.AspNetCore.Mvc.ApiVersion.Parse("8.0");
                     options.ReportApiVersions = true;
                 });
            services.AddVersionedApiExplorer(options =>
            {

                // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
                // note: the specified format code will format the version as "'v'major[.minor][-status]"
                options.GroupNameFormat = "'v'VVV";

                // note: this option is only necessary when versioning by url segment. the SubstitutionFormat
                // can also be used to control the format of the API version in route templates

                //THIS IS WHAT ADDS THE API version PARAMETER AUTOMATICALLY so you don't need to type an 8 in every swagger-ui route test
                options.SubstituteApiVersionInUrl = true;
            });
            services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
            services.AddSwaggerGen(
                c =>
                {
                    // integrate xml comments
                    c.IncludeXmlComments(XmlCommentsFilePath);

                    //https://stackoverflow.com/questions/56234504/migrating-to-swashbuckle-aspnetcore-version-5
                    //First we define the security scheme
                    c.AddSecurityDefinition("Bearer", //Name the security scheme
                        new OpenApiSecurityScheme
                        {
                            Description = "JWT Authorization header using the Bearer scheme.",
                            Type = SecuritySchemeType.Http, //We set the scheme type to http since we're using bearer authentication
                            Scheme = "bearer" //The name of the HTTP Authorization scheme to be used in the Authorization header. In this case "bearer".
                        });

                    c.AddSecurityRequirement(new OpenApiSecurityRequirement{
                                    {
                                        new OpenApiSecurityScheme{
                                            Reference = new OpenApiReference{
                                                Id = "Bearer", //The name of the previously defined security scheme.
                                                Type = ReferenceType.SecurityScheme
                                            }
                                        },new List<string>()
                                    }
                                });
                }
            );
            services.AddSwaggerGenNewtonsoftSupport();

            #endregion

            #region JWT AUTHENTICATION
            //get the key if specified
            var secretKey = ServerBootConfig.SOCKEYE_JWT_SECRET;

            //If no key specified make a unique one
            //This means the jwt creds won't survive a server reboot
            //so in that case users need to specify an Sockeye_JWT_SECRET environment variable
            if (string.IsNullOrWhiteSpace(secretKey))
            {
                _newLog.LogWarning("SOCKEYE_JWT_SECRET configuration setting is missing; Sockeye will randomly generate one. Any Users who were logged in when the server restarted will need to login to get a fresh auth token. See manual 'SOCKEYE_JWT_SECRET' page for details.");
                secretKey = Util.Hasher.GenerateSalt();
            }
            //WAS "UNLICENSED5G*QQJ8#bQ7$Xr_@sXfHq4"


            //If secretKey is less than 32 characters, pad it
            if (secretKey.Length < 32)
            {
                secretKey = secretKey.PadRight(32, '-');
            }

            ServerBootConfig.SOCKEYE_JWT_SECRET = secretKey;
            var signingKey = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.SOCKEYE_JWT_SECRET));

            _newLog.LogDebug("Authorization");
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
                {
                    // options.AutomaticAuthenticate = true;
                    // options.AutomaticChallenge = true;
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        // Token signature will be verified using a private key.
                        ValidateIssuerSigningKey = true,
                        RequireSignedTokens = true,
                        IssuerSigningKey = signingKey,
                        ValidateIssuer = true,
                        ValidIssuer = "rockfish.ayanova.com",
                        ValidateAudience = false,
                        //ValidAudience = "http://localhost:7575/"

                        // Token will only be valid if not expired yet, with 5 minutes clock skew.
                        ValidateLifetime = true,
                        RequireExpirationTime = true,
                        ClockSkew = TimeSpan.Zero//new TimeSpan(0, 0, 2),
                    };
                });



            #endregion




            _newLog.LogDebug("Generator");
            services.AddSingleton<IHostedService, GeneratorService>();

        }



        ////////////////////////////////////////////////////////////
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        //
        public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IWebHostEnvironment env,
        AyContext dbContext, IApiVersionDescriptionProvider provider, Sockeye.Api.ControllerHelpers.ApiServerState apiServerState,
        IServiceProvider serviceProvider)
        {
            _newLog.LogDebug("Configuring request pipeline...");

            //this *may* be useful in the event of an issue so uncomment if necessary but errors during dev are handled equally by the logging, I think
            // if (env.IsDevelopment())
            // {
            //     app.UseDeveloperExceptionPage();
            // }

            //Store a reference to the dependency injection service for static classes 
            ServiceProviderProvider.Provider = app.ApplicationServices;

            //Enable ability to handle reverse proxy
            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor | Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto
            });


            //If want to add a header to all (or filtered in here) requests
            // app.Use(async (context, next) =>
            // {
            //     context.Response.Headers.Add("X-Developed-By", "Ground Zero Tech-Works inc.");
            //     await next.Invoke();
            // });


            #region STATIC FILES
            _newLog.LogDebug("Static files");
            app.UseDefaultFiles();
            app.UseStaticFiles();
            //Might need the following if the page doesn't update in the client properly
            //however the vue build process will automatically uniquify each build file names so maybe not required
            // app.UseStaticFiles(new StaticFileOptions
            // {
            //     OnPrepareResponse = context =>
            //     {
            //         if (context.File.Name == "kindex.html")
            //         {
            //             context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
            //             context.Context.Response.Headers.Add("Expires", "-1");
            //         }
            //     }
            // });
            #endregion

            _newLog.LogDebug("Routing pipeline");
            app.UseRouting();//this wasn't here for 2.2 but added for 3.0, needs to come before the stuff after 

            _newLog.LogDebug("CORS pipeline");
            app.UseCors("CorsPolicy");


            #region AUTH / ROLES CUSTOM MIDDLEWARE
            _newLog.LogDebug("Authentication pipeline");
            //Use authentication middleware
            app.UseAuthentication();

            _newLog.LogDebug("Authorization pipeline");
            app.UseAuthorization();


            //Custom middleware to ensure token still valid and to
            //get user roles and put them into the request so 
            //they can be authorized in routes.
            app.Use(async (context, next) =>
            {
                if (!context.User.Identity.IsAuthenticated)
                {
                    #region Profiler workaround 
                    //Is this a profiler route?  If so we're going to use the dl token to authorize
                    if (context.Request.Path.Value.StartsWith("/profiler/results"))
                    {
                        //someone is requesting the profiler
                        //check for a dl token "t" and rehydrate user if found
                        //Note that the profiler UI triggers it's own requests to to get the token
                        //we need to check the referer which was the first page of profile

                        string token = string.Empty;
                        //is the token in the request?
                        if (context.Request.Query.ContainsKey("t"))
                        {
                            token = context.Request.Query["t"].ToString();
                        }
                        else if (context.Request.Headers["Referer"].Count > 0)
                        {//Maybe it's in the referer
                         //try to split it on the ?
                            string[] stuff = context.Request.Headers["Referer"].ToString().Split('?');
                            if (stuff.Count() > 1)
                            {
                                var q = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(stuff[1]);
                                if (q.ContainsKey("t"))
                                {
                                    token = q["t"].ToString();
                                }
                            }
                        }

                        if (!string.IsNullOrWhiteSpace(token))
                        {
                            using (AyContext ct = ServiceProviderProvider.DBContext)
                            {
                                var u = ct.User.AsNoTracking().SingleOrDefault(z => z.DlKey == token.ToString() && z.Active == true);
                                if (u != null)
                                {
                                    //this is necessary because they might have an expired JWT but this would just keep on working without a date check
                                    //the default is the same timespan as the jwt so it's all good
                                    var utcNow = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero);
                                    if (u.DlKeyExpire > utcNow.DateTime)
                                    {
                                        if (Sockeye.Api.ControllerHelpers.Authorized.HasReadFullRole(u.Roles, SockType.ServerMetrics))
                                            context.Request.HttpContext.Items["AY_PROFILER_ALLOWED"] = true;
                                    }
                                }
                            }
                        }
                    }
                    #endregion profiler workaround

                    context.Request.HttpContext.Items["AY_ROLES"] = 0;
                    await next.Invoke();
                }
                else
                {
                    //Get user ID from claims
                    long userId = Convert.ToInt64(context.User.FindFirst(c => c.Type == "id").Value);

                    //Get JWT
                    string JWT = string.Empty;
                    var AuthHeaders = context.Request.Headers[Microsoft.Net.Http.Headers.HeaderNames.Authorization];
                    foreach (String s in AuthHeaders)
                    {
                        if (s.ToLowerInvariant().Contains("bearer"))
                        {
                            JWT = s.Split(' ')[1];
                            break;
                        }
                    }

                    //Get the database context
                    var ct = context.RequestServices.GetService<AyContext>();

                    //get the user record
                    var u = await ct.User.AsNoTracking().Where(a => a.Id == userId).Select(m => new { roles = m.Roles, name = m.Name, m.UserType, id = m.Id, translationId = m.UserOptions.TranslationId, currentAuthToken = m.CurrentAuthToken }).FirstAsync();
                    context.Request.HttpContext.Items["AY_ROLES"] = u.roles;
                    context.Request.HttpContext.Items["AY_USERNAME"] = u.name;
                    context.Request.HttpContext.Items["AY_USER_ID"] = u.id;
                    context.Request.HttpContext.Items["AY_TRANSLATION_ID"] = u.translationId;
                    context.Request.HttpContext.Items["AY_USER_TYPE"] = u.UserType;

                    var currentAuthToken = u.currentAuthToken;

                    //turned out didn't need this for v8 migrate so far, but keeping in case it turns out to be handy down the road
                    // //Is import mode header set?
                    // if (context.Request.Headers.ContainsKey("X-AY-Import-Mode"))
                    //     context.Request.HttpContext.Items["AY_IMPORT_MODE"] = true;


                    //CHECK JWT 
                    if (
                        !context.Request.Path.Value.EndsWith("/auth") &&
                        !context.Request.Path.Value.EndsWith("notify/hello") &&
                        u.currentAuthToken != JWT
                    )//except "/api/v8/auth" and prelogin notify/hello routes so user can login
                    {

                        //It may be a local report render request from a job
                        bool bLocalReportRenderRequest = false;
                        if (context.Request.Host.Host == "127.0.0.1")
                        {
                            //check if token has j value set
                            if (context.Request.IsLocal())
                            {
                                //if this is a generate report internal token this will be the override language set
                                //and it's existance is enough to indicate it was set internally by this server
                                var rpl = context.User.Claims.FirstOrDefault(c => c.Type == "rpl");
                                if (rpl != null)
                                {
                                    //it's a local request, the jwt already passed earlier so we issued it and it has the correct claim set so allow it
                                    bLocalReportRenderRequest = true;
                                    //set override language
                                    context.Request.HttpContext.Items["AY_TRANSLATION_ID"] = Convert.ToInt64(rpl.Value);
                                    await next.Invoke();
                                }
                            }
                        }
                        if (!bLocalReportRenderRequest)
                        {
                            context.Response.StatusCode = 401;
                            context.Response.Headers.Add("X-Sockeye-Authorization-Error", "E2004 - Authorization token replaced by more recent login");
                            await context.Response.WriteAsync("E2004 - Authorization token replaced by more recent login");
                        }
                    }
                    else
                    {
                        await next.Invoke();
                    }
                }

            });

            #endregion


            _newLog.LogDebug("Profiler");
            app.UseMiniProfiler();



            _newLog.LogDebug("Endpoints pipeline");
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapHealthChecks("/health");
                endpoints.MapControllers();
            });

            #region SWAGGER

            _newLog.LogDebug("API explorer pipeline");
            // Enable middleware to serve generated Swagger as a JSON endpoint.          
            app.UseSwagger();
            app.UseSwaggerUI(
                   options =>
                   {
                       foreach (var description in provider.ApiVersionDescriptions)
                       {
                           options.SwaggerEndpoint(
                               $"/swagger/{description.GroupName}/swagger.json",
                               description.GroupName.ToUpperInvariant());
                       }
                       options.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
                       options.DefaultModelsExpandDepth(-1);//This is meant to hide the Models section that would appear at the bottom of the swagger ui showing *all* models from the api
                       options.DocumentTitle = "Sockeye API explorer";
                       options.RoutePrefix = "api-docs";
                   });


            #endregion swagger




            if (ServerBootConfig.SOCKEYE_PERMANENTLY_ERASE_DATABASE)
            {
                _newLog.LogWarning("SOCKEYE_PERMANENTLY_ERASE_DATABASE has been set - deleting and recreating database");
                Util.DbUtil.DropAndRecreateDbAsync(_newLog).Wait();
                AySchema.CheckAndUpdateAsync(dbContext, _newLog).Wait();
            }



            var dbServerVersionInfo = DbUtil.DBServerVersion(dbContext);
            var dbServerRunTimeParameters = DbUtil.DBServerRunTimeParameters(dbContext);
            //Log server version
            _newLog.LogInformation("Database server version - {0}", dbServerVersionInfo);

            //db server extended parameters
            _newLog.LogTrace($"Database server runtime parameters{Environment.NewLine}{string.Join(Environment.NewLine, dbServerRunTimeParameters)}");

            //log each item individually from runtime parameters
            ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO.Add("DB SERVER", dbServerVersionInfo);
            foreach (var p in dbServerRunTimeParameters)
                ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO.Add(p.Key, p.Value);


            //Check schema
            _newLog.LogDebug("DB schema check");
            AySchema.CheckAndUpdateAsync(dbContext, _newLog).Wait();


            //Check database integrity
            _newLog.LogDebug("DB integrity check");
            DbUtil.CheckFingerPrintAsync(AySchema.EXPECTED_COLUMN_COUNT,
                                         AySchema.EXPECTED_INDEX_COUNT,
                                         AySchema.EXPECTED_CHECK_CONSTRAINTS,
                                         AySchema.EXPECTED_FOREIGN_KEY_CONSTRAINTS,
                                         AySchema.EXPECTED_VIEWS,
                                         AySchema.EXPECTED_ROUTINES,
                                         _newLog).Wait();


            //Set static global biz settings
            _newLog.LogDebug("Global settings");
            ServerGlobalBizSettings.Initialize(null, dbContext);

            _newLog.LogDebug("Ops settings");
            ServerGlobalOpsSettingsCache.Initialize(dbContext);

            //Ensure translations are present, not missing any keys and that there is a server default translation that exists          
            TranslationBiz lb = new TranslationBiz(dbContext, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.OpsAdmin);
            lb.ValidateTranslationsAsync().Wait();


            //SPA FALLBACK ROUTE                 
            app.Use(async (context, next) =>
            {
                //to support html5 pushstate routing in spa
                //this ensures that a refresh at the client will not 404 but rather force back to the index.html app page and then handled internally by the client 
                await next();
                if (!context.Response.HasStarted && !context.Request.Path.Value.StartsWith("/api") && context.Request.Path.Value != "/docs" && context.Request.Path.Value != "/cust" && context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
                {
                    context.Request.Path = "/index.html";
                    context.Response.StatusCode = 200;
                    context.Response.ContentType = "text/html";
                    await context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "index.html"));
                }
            });


            //Log schema version into server log (would only otherwise log if schema was out of date)
            _newLog.LogInformation($"DB Schema version - {Sockeye.Util.AySchema.currentSchema}");
            ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("DB Schema version", Sockeye.Util.AySchema.currentSchema.ToString());



            //Check for SuperUser password override
            if (!string.IsNullOrWhiteSpace(ServerBootConfig.SOCKEYE_SET_SUPERUSER_PW))
            {
                _newLog.LogWarning($"### SOCKEYE_SET_SUPERUSER_PW IS PRESENT - RESETTING SUPERUSER PASSWORD NOW... ###");
                Sockeye.Biz.UserBiz.ResetSuperUserPassword();
                _newLog.LogWarning($"### SOCKEYE_SET_SUPERUSER_PW HAS BEEN USED TO RESET SUPER USER PASSWORD YOU CAN REMOVE THIS SETTING NOW ###");
            }

            //Boot lock for generator
            ServerGlobalOpsSettingsCache.BOOTING = false;

            //Open up the server for visitors
            _newLog.LogDebug("Setting server state open");
            apiServerState.SetOpen();

            //final startup log
            _newLog.LogInformation("Boot complete - server open");

            //flag in console that server is open
            Console.WriteLine("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-");
            Console.WriteLine("BOOT: COMPLETED - SERVER OPEN");
            Console.WriteLine($"SOCKEYE_USE_URLS setting: \"{ServerBootConfig.SOCKEYE_USE_URLS}\"");
            Console.WriteLine("Controlled shutdown: Sockeye APP -> \"Operations\" -> \"ServerState\" -> \"Shut down server\" from menu");
            Console.WriteLine("Forced shutdown: Ctrl+C keyboard shortcut");
            Console.WriteLine("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-");


            if (ServerBootConfig.SOCKEYE_PERMANENTLY_ERASE_DATABASE)
            {
                _newLog.LogWarning("SOCKEYE_PERMANENTLY_ERASE_DATABASE has been set - IMPORTING ROCKFISH");
                GlobalBizSettingsBiz.ImportRockfish(_newLog).Wait();
                _newLog.LogInformation("IMPORT COMPLETED");
            }
        }


        #region Swagger and API Versioning utilities

        static string XmlCommentsFilePath
        {
            get
            {
                var basePath = AppContext.BaseDirectory;
                var fileName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name + ".xml";
                return Path.Combine(basePath, fileName);
            }
        }


        #endregion
    }
}

