using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

using NLog.Web;
using NLog.Targets;
using NLog.Config;

using App.Metrics;
using App.Metrics.AspNetCore;

using AyaNova.Util;
using AyaNova.Api.ControllerHelpers;

namespace AyaNova
{

    public class Program
    {

        public static void Main(string[] args)
        {

            //Get config
            var config = new ConfigurationBuilder().AddEnvironmentVariables().AddCommandLine(args).Build();
            ServerBootConfig.SetConfiguration(config);

            #region Initialize Logging                     

            //default log level
            NLog.LogLevel NLogLevel = NLog.LogLevel.Info;
            bool logLevelIsInfoOrHigher = true;

            switch (ServerBootConfig.AYANOVA_LOG_LEVEL.ToLowerInvariant())
            {
                case "fatal":
                    NLogLevel = NLog.LogLevel.Fatal;
                    break;
                case "error":
                    NLogLevel = NLog.LogLevel.Error;
                    break;
                case "warn":
                    NLogLevel = NLog.LogLevel.Warn;
                    break;
                case "info":
                    NLogLevel = NLog.LogLevel.Info;
                    break;
                case "debug":
                    NLogLevel = NLog.LogLevel.Debug;
                    logLevelIsInfoOrHigher = false;
                    break;
                case "trace":
                    NLogLevel = NLog.LogLevel.Trace;
                    logLevelIsInfoOrHigher = false;
                    break;
                default:
                    NLogLevel = NLog.LogLevel.Info;
                    break;
            }


            // Step 1. Create configuration object 
            var logConfig = new LoggingConfiguration();

            // Step 2. Create targets and add them to the configuration 
            var fileTarget = new FileTarget();
            logConfig.AddTarget("file", fileTarget);

            //console target for really serious errors only
            var consoleTarget = new ConsoleTarget();
            logConfig.AddTarget("console", consoleTarget);

            var nullTarget = new NLog.Targets.NullTarget();
            logConfig.AddTarget("blackhole", nullTarget);

            // Step 3. Set target properties 

            fileTarget.FileName = Path.Combine(ServerBootConfig.AYANOVA_LOG_PATH, "log-ayanova.txt");
            fileTarget.Layout = "${longdate}|${uppercase:${level}}|${logger}|${message:exceptionSeparator==>:withException=true}";
            fileTarget.ArchiveFileName = Path.Combine(ServerBootConfig.AYANOVA_LOG_PATH, "log-ayanova-{#}.txt");
            fileTarget.ArchiveEvery = FileArchivePeriod.Wednesday;
            fileTarget.MaxArchiveFiles = 3;

            // Step 4. Define rules

            //filter out all Microsoft INFO level logs as they are too much
            var logRuleFilterOutMicrosoft = new LoggingRule("Microsoft.*", NLog.LogLevel.Trace, NLog.LogLevel.Info, nullTarget);
            logRuleFilterOutMicrosoft.Final = true;

            //filter out all Microsoft EF CORE concurrency exceptions, it's a nuisance unless debugging or something
            //This is what I have to filter because it's the top exception: Microsoft.EntityFrameworkCore.Update
            //But this is what I'm actually trying to filter: Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException
            //however there doesn't appear to be a way to filter out based on content so...

            var logRuleFilterOutMicrosoftEfCoreConcurrencyExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.Update", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget);
            logRuleFilterOutMicrosoftEfCoreConcurrencyExceptions.Final = true;

            var logRuleFilterOutMicrosoftEfCoreCommandExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.Database.Command", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget);
            logRuleFilterOutMicrosoftEfCoreCommandExceptions.Final = true;

            var logRuleFilterOutMicrosoftEfCoreDbUpdateExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.DbUpdateException", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget);
            logRuleFilterOutMicrosoftEfCoreDbUpdateExceptions.Final = true;



            //Log all other regular items at selected level
            var logRuleAyaNovaItems = new LoggingRule("*", NLogLevel, fileTarget);

            //Log error or above to console
            var logRuleForConsole = new LoggingRule("*", NLog.LogLevel.Error, consoleTarget);

            //add console serious error only log rule
            logConfig.LoggingRules.Add(logRuleForConsole);

            //only log microsoft stuff it log is debug level or lower
            if (logLevelIsInfoOrHigher)
            {
                //filter OUT microsoft stuff
                logConfig.LoggingRules.Add(logRuleFilterOutMicrosoft);
                logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreConcurrencyExceptions);
                logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreCommandExceptions);
                logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreDbUpdateExceptions);
            }

            logConfig.LoggingRules.Add(logRuleAyaNovaItems);


            //Turn on internal logging:
            if (ServerBootConfig.AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG)
            {
                NLog.Common.InternalLogger.LogFile = "log-ayanova-logger.txt";
                NLog.Common.InternalLogger.LogLevel = NLog.LogLevel.Debug;
            }

            // NLog: setup the logger first to catch all errors         
            var logger = NLogBuilder.ConfigureNLog(logConfig).GetCurrentClassLogger();

            //This is the first log entry
            logger.Info("AYANOVA SERVER BOOTING (log level: \"{0}\")", ServerBootConfig.AYANOVA_LOG_LEVEL);
            logger.Info(AyaNovaVersion.FullNameAndVersion);
            logger.Debug("BOOT: Log path is \"{0}\" ", ServerBootConfig.AYANOVA_LOG_PATH);

            if (ServerBootConfig.AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG)
            {
                logger.Warn("BOOT: AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG is enabled! Disable as soon as no longer required.");
            }

            //Log environmental settings
            logger.Info("BOOT: OS - {0}", Environment.OSVersion.ToString());
            logger.Debug("BOOT: Machine - {0}", Environment.MachineName);
            logger.Debug("BOOT: User - {0}", Environment.UserName);
            logger.Debug("BOOT: .Net Version - {0}", Environment.Version.ToString());
            logger.Debug("BOOT: CPU count - {0}", Environment.ProcessorCount);
            logger.Debug("BOOT: Default language - \"{0}\"", ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE);

            #endregion


            //Ensure we are in the correct folder
            string startFolder = Directory.GetCurrentDirectory();
            var wwwRootFolder = Path.Combine(startFolder, "wwwroot");


            //Test for web root path
            //If user starts AyaNova from folder that is not the contentRoot then 
            //AyaNova won't be able to serve static files
            if (!Directory.Exists(wwwRootFolder))
            {
                var err = string.Format("BOOT: E1010 - AyaNova was not started in the correct folder.  AyaNova must be started from the folder that contains the \"wwwroot\" folder but was started instead from this folder: \"{0}\" which does not contain the wwwroot folder.", startFolder);
                logger.Fatal(err);
                throw new System.ApplicationException(err);
            }

            try
            {
                BuildWebHost(args, logger).Run();
            }
            catch (Exception e)
            {
                logger.Fatal(e, "BOOT: E1090 - AyaNova server can't start due to unexpected exception during initialization");
                throw;
            }
        }



        public static IWebHost BuildWebHost(string[] args, NLog.Logger logger)
        {
            logger.Debug("BOOT: building web host");
            var configuration = new ConfigurationBuilder().AddCommandLine(args).Build();

            return WebHost.CreateDefaultBuilder(args)
                .CaptureStartupErrors(true)
                .UseSetting("detailedErrors", "true")
                .UseUrls(ServerBootConfig.AYANOVA_USE_URLS)//default port and urls, set first can be overridden by any later setting here                              
                .UseConfiguration(configuration)//support command line override of port (dotnet run urls=http://*:8081)
                .UseIISIntegration()//support IIS integration just in case, it appears here to override prior settings if necessary (port)                                                   
                .ConfigureMetricsWithDefaults(builder =>
                {
                    if (ServerBootConfig.AYANOVA_METRICS_USE_INFLUXDB)
                    {
                        builder.Report.ToInfluxDb(
                            options =>
                            {

                                options.InfluxDb.BaseUri = new Uri(ServerBootConfig.AYANOVA_METRICS_INFLUXDB_BASEURL);
                                options.InfluxDb.Database = ServerBootConfig.AYANOVA_METRICS_INFLUXDB_DBNAME;
                                if (!string.IsNullOrWhiteSpace(ServerBootConfig.AYANOVA_METRICS_INFLUXDB_CONSISTENCY))
                                {
                                    options.InfluxDb.Consistenency = ServerBootConfig.AYANOVA_METRICS_INFLUXDB_CONSISTENCY;
                                }
                                options.InfluxDb.UserName = ServerBootConfig.AYANOVA_METRICS_INFLUXDB_USERNAME;
                                options.InfluxDb.Password = ServerBootConfig.AYANOVA_METRICS_INFLUXDB_PASSWORD;
                                if (!string.IsNullOrWhiteSpace(ServerBootConfig.AYANOVA_METRICS_INFLUXDB_RETENSION_POLICY))
                                {
                                    options.InfluxDb.RetensionPolicy = ServerBootConfig.AYANOVA_METRICS_INFLUXDB_RETENSION_POLICY;
                                }

                                options.InfluxDb.CreateDataBaseIfNotExists = true;
                                options.HttpPolicy.BackoffPeriod = TimeSpan.FromSeconds(30);
                                options.HttpPolicy.FailuresBeforeBackoff = 5;
                                options.HttpPolicy.Timeout = TimeSpan.FromSeconds(10);
                                //options.MetricsOutputFormatter = new App.Metrics.Formatters.Json.MetricsJsonOutputFormatter();
                                //options.Filter = filter;
                                options.FlushInterval = TimeSpan.FromSeconds(20);
                            });
                    }

                })
                .UseMetricsEndpoints(opt =>
                {
                    opt.EnvironmentInfoEndpointEnabled = false;
                    opt.MetricsEndpointEnabled = false;
                    opt.MetricsTextEndpointEnabled = false;
                })
                .UseMetrics()
                .UseStartup<Startup>()
                .ConfigureLogging((context, logging) =>
                {
                    // clear all previously registered providers
                    //https://stackoverflow.com/a/46336988/8939
                    logging.ClearProviders();
                })
                .UseNLog() // NLog: setup NLog for Dependency injection              
                .Build();
        }

    }//eoc

}//eons
