using HRD.AppLogger;
using NLog;
using NLog.Config;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.IO;

namespace HRD.AppLogger
{
    public enum EN_LoggingLevel
    {
        Trace = 1,
        Debug = 2,
        Info = 3,
        Warn = 4,
        Error = 5,
        Fatal = 6,
        Off = 7
    }
}

public static class AppLoggerConfig
{
    private static readonly string DBLOG_CommandText =
       @"
            INSERT INTO dbo.DBLog
            (   MachineName,
                Application,
                Logged,
                Level,
                Message,
                Logger,
                CallSite,
                Webrequest,
                Stacktrace,
                InnerException,
                Exception,
                Version,
                Entity
            )
            VALUES
            (   @machineName,
                @application,
                @logged,
                @level,
                @message,
                @logger,
                @callsite,
                iif(@webrequestaction='',@webrequest, concat(@webrequest,' | action:',@webrequestaction)),
                @stacktrace,
                @innerException,
                @exception,
                @Version,
                @Entity
            );";

    private static readonly Dictionary<string, string> DBLOG_Parameters = new Dictionary<string, string>() {
            //Globals
            {"@version", "${gdc:item=Version}"},
            {"@application", "${gdc:item=Application}"},

            //Local
            //{"@webrequest", "${mdlc:item=Webrequest}"},
            {"@entity", "${mdlc:item=Entity}"},

            //WebApi
            {"@webrequest", "${aspnet-request-url}"},
            {"@webrequestaction", "${aspnet-mvc-action}"},

            //Nlog
            {"@machineName", "${machinename}" },
            {"@logged", "${date}"},
            {"@level", "${level:upperCase=true}"},
            {"@message", "${message}"},
            {"@logger", "${logger}"},
            {"@callSite", "${callsite:fileName=true:includeSourcePath=false:skipFrames=1}"},             //{"@callSite", "${callsite:filename=false:className=true:methodName=true}"},
            {"@stacktrace", "${stacktrace:topFrames=10}"},
            {"@InnerException", "${exception:format=Message,StackTrace,Data:maxInnerExceptionLevel=10}"},
            {"@Exception", "${exception:format=ToString}"},
            };

    public static string AssemblyName { get; set; }
    public static string AssemblyVersion { get; set; }
    public static EN_LoggingLevel DBLogLevel { get; set; } = EN_LoggingLevel.Warn;
    public static EN_LoggingLevel FileLogLevel { get; set; } = EN_LoggingLevel.Error;
    public static string LogDirectory { get; set; } = String.Empty;
    public static string NlogConnectionstring { get; set; }
    public static LogLevel NlogDBLogLevel { get; set; }
    public static LogLevel NlogFileLogLevel { get; set; }
    public static NlogSentryConfig NlogSentryConfig { get; set; } = new NlogSentryConfig();

    public static LoggingConfiguration CreateConfig()
    {
        NlogDBLogLevel = MapLogLevel(DBLogLevel);
        NlogFileLogLevel = MapLogLevel(FileLogLevel);

        var config = new LoggingConfiguration();

        DatabaseTarget dbTarget = CreateDatabaseTarget(config);
        var dbRule = new LoggingRule("*", NlogDBLogLevel, dbTarget);
        config.LoggingRules.Add(dbRule);

        //Rules
        FileTarget targetFile = CreateFileTarget(config);
        var rule = new LoggingRule("*", NlogFileLogLevel, targetFile);
        config.LoggingRules.Add(rule);
        if (NlogSentryConfig.NlogSentryIsEnable)
        {
            AddSentry(config);
        }

        return config;
    }

    public static void Init(
            string assemblyName,
        string assemblyVersion,
        EN_LoggingLevel dbLogLevel = EN_LoggingLevel.Warn
       , EN_LoggingLevel fileLoogLevel = EN_LoggingLevel.Off
       , string logDirectory = "")
    {
        AssemblyVersion = assemblyVersion;
        AssemblyName = assemblyName;
        DBLogLevel = dbLogLevel;
        FileLogLevel = fileLoogLevel;
        LogDirectory = logDirectory;
    }

    private static void AddSentry(LoggingConfiguration config)
    {
        config.AddSentry(o =>
        {
            o.Dsn = NlogSentryConfig.Dsn;

            o.Layout = "${message}";

            o.BreadcrumbLayout = "${logger}: ${message} #url:  ${aspnet-request-url} | #action: ${aspnet-mvc-action}";
            // Optionally specify a separate format for breadcrumbs

            o.MinimumBreadcrumbLevel = NlogSentryConfig.MinimumBreadcrumbLevel; // Debug and higher are stored as breadcrumbs (default is Info)
            o.MinimumEventLevel = NlogSentryConfig.MinimumEventLevel; // Error and higher is sent as event (default is Error)

            // If DSN is not set, the SDK will look for an environment variable called SENTRY_DSN. If
            // nothing is found, SDK is disabled.

            o.AttachStacktrace = NlogSentryConfig.AttachStacktrace;
            o.SendDefaultPii = NlogSentryConfig.SendDefaultPii; // Send Personal Identifiable information like the username of the user logged in to the device

            o.IncludeEventDataOnBreadcrumbs = NlogSentryConfig.IncludeEventDataOnBreadcrumbs; // Optionally include event properties with breadcrumbs
            o.ShutdownTimeoutSeconds = 5;
            o.TracesSampleRate = 0.3;

            //Optionally specify user properties via NLog (here using MappedDiagnosticsLogicalContext as an example)
            //         o.User = new SentryNLogUser
            //         {
            //             Id = "${mdlc:item=id}",
            //             Username = "${mdlc:item=username}",
            //             Email = "${mdlc:item=email}",
            //             IpAddress = "${mdlc:item=ipAddress}",
            //             Other =
            //{
            //        new TargetPropertyWithContext("mood", "joyous")
            //},
            //};

            o.AddTag("Backend", "${gdc:item=Application}");  // Send the logger name as a tag
            o.AddTag("BackendVersion", "${gdc:item=Version}");  // Send the logger name as a tag
            o.AddTag("logger", "${logger}");  // Send the logger name as a tag

            // Other configuration
        });
    }

    private static DatabaseTarget CreateDatabaseTarget(LoggingConfiguration config)
    {
        var dbTarget = new DatabaseTarget
        {
            ConnectionString = NlogConnectionstring,

            CommandText = DBLOG_CommandText
        };

        foreach (var item in DBLOG_Parameters)
        {
            dbTarget.Parameters.Add(new DatabaseParameterInfo(item.Key, item.Value));
        }

        config.AddTarget("database", dbTarget);
        return dbTarget;
    }

    private static FileTarget CreateFileTarget(LoggingConfiguration config)
    {
        var targetFile =
            new FileTarget
            {
                FileName = Path.Combine(
                    !string.IsNullOrEmpty(LogDirectory) ? LogDirectory : "${basedir}/_logs/",
                    "${gdc:item=Application}" + "_V." + "${gdc:item=Version}" + $"_{DateTime.Now:yyyyMMdd}.log"),
                Layout = "${date}|${uppercase:${level}}|${stacktrace}|${message}" +
                "|${exception:format=Message,StackTrace,Data:maxInnerExceptionLevel=10}" +
                "${event-properties:item=EventId.Id}"
            };

        config.AddTarget("logfile", targetFile);
        return targetFile;
    }

    private static LogLevel MapLogLevel(EN_LoggingLevel loggingLevel)
    {
        LogLevel logLevel = LogLevel.Error;
        switch (loggingLevel)
        {
            case EN_LoggingLevel.Trace:
                logLevel = LogLevel.Trace;
                break;

            case EN_LoggingLevel.Debug:
                logLevel = LogLevel.Debug;
                break;

            case EN_LoggingLevel.Info:
                logLevel = LogLevel.Info;
                break;

            case EN_LoggingLevel.Warn:
                logLevel = LogLevel.Warn;
                break;

            case EN_LoggingLevel.Error:
                logLevel = LogLevel.Error;
                break;

            case EN_LoggingLevel.Fatal:
                logLevel = LogLevel.Fatal;
                break;

            case EN_LoggingLevel.Off:
                logLevel = LogLevel.Off;
                break;

            default:
                break;
        }
        return logLevel;
    }

    public static void SetSentryUser(int id, string login, string email, IDictionary<string, string> others = null)
    {
        NlogSentryConfig.SentryUser = new Sentry.User { Id = id.ToString(), Username = login, Email = email };
        if (others != null) NlogSentryConfig.SentryUser.Other = others;
        Sentry.SentrySdk.ConfigureScope(scope => { scope.User = NlogSentryConfig.SentryUser; });
    }
}