Imports System.Net.Http
Imports System.IO
Imports Microsoft.Extensions.Logging
Imports System.Threading.Tasks
Imports System.Configuration

'// Ejemplo de uso de la extensión LogVariable():
'//
'// ' Registrar información de una variable
'// Dim peassoVariable As String = "Hello, World!"
'// logger.LogVariable("peassoVariable", peassoVariable, LogLevel.Debug)

'// El uso del destino Pushover está limitado a danmun.

Public NotInheritable Class TsLoggerConfiguration
    '// Identificador del evento a registrar (0 para todos).
    Public Property EventId As Integer = 0
    '// Nivel mínimo de registro.
    Private Property _minimumLogLevel As LogLevel = LogLevel.Trace
    Public Property MinimumLogLevel As String
        Get
            Return _minimumLogLevel
        End Get
        Set(logLevelString As String)
            If Not [Enum].TryParse(logLevelString, True, _minimumLogLevel) Then
                '// Valor predeterminado en caso de que no se pueda
                '// interpretar la configuración.
                _minimumLogLevel = LogLevel.Trace
            End If
        End Set
    End Property
    '// Indica si se debe registrar en la consola.
    Public Property LogToConsole As Boolean = False
    '// Indica si se debe registrar en la salida de depuración.
    Public Property LogToDebug As Boolean = True
    '// Indica si se debe registrar en un archivo.
    Public Property LogToFile As Boolean = False
    '// Ruta base para los archivos de registro (debe ser válida y accesible).
    Public Property LogFilePath As String
    Public Property LogToPushover As Boolean = False
    Public Property PushoverMinimumLogLevel As LogLevel = LogLevel.Error
    Public Property LogToSlack As Boolean = False
    Public Property SlackDestination As String = "#notificaciones"
    Public Property NetworkInSeparateThread As Boolean = False
    Public Property IncludeSourceInfo As Boolean = False
    '// Mapeo entre los niveles de registro y los colores de la consola.
    Friend ReadOnly Property LogLevelToColorMap As Dictionary(Of LogLevel, ConsoleColor) = New Dictionary(Of LogLevel, ConsoleColor) From {
        {LogLevel.Trace, ConsoleColor.DarkCyan},
        {LogLevel.Debug, ConsoleColor.Cyan},
        {LogLevel.Information, ConsoleColor.Green},
        {LogLevel.Warning, ConsoleColor.DarkYellow},
        {LogLevel.Error, ConsoleColor.Red},
        {LogLevel.Critical, ConsoleColor.Magenta}
    }
End Class

Public NotInheritable Class TsLogger
    Implements ILogger

    Private ReadOnly _name As String
    Private ReadOnly _getCurrentConfig As Func(Of TsLoggerConfiguration)
    Private ReadOnly _config As TsLoggerConfiguration

    '// Constructor con nombre y función para obtener la configuración actual.
    Public Sub New(name As String, getCurrentConfig As Func(Of TsLoggerConfiguration))
        If String.IsNullOrEmpty(name) Then Throw New ArgumentException("El nombre no puede estar vacío", NameOf(name))
        If getCurrentConfig Is Nothing Then Throw New ArgumentNullException(NameOf(getCurrentConfig))
        _name = name
        _getCurrentConfig = getCurrentConfig
    End Sub

    '// Constructor con nombre y configuración directa.
    Public Sub New(name As String, config As TsLoggerConfiguration)
        If String.IsNullOrEmpty(name) Then Throw New ArgumentException("El nombre no puede estar vacío", NameOf(name))
        If config Is Nothing Then Throw New ArgumentNullException(NameOf(config))
        _name = name
        _config = config
    End Sub

    '// Obtener la configuración actual.
    Private Function GetCurrentConfiguration() As TsLoggerConfiguration
        Return If(_getCurrentConfig IsNot Nothing, _getCurrentConfig.Invoke(), _config)
    End Function

    '// Verifica si el nivel de registro está habilitado.
    Public Function IsEnabled(logLevel As LogLevel) As Boolean Implements ILogger.IsEnabled
        Return logLevel >= GetCurrentConfiguration().MinimumLogLevel
    End Function

    '// Registra un mensaje con la configuración dada.
    Public Sub Log(Of TState)(logLevel As LogLevel, eventId As EventId, state As TState, exception As Exception, formatter As Func(Of TState, Exception, String)) Implements ILogger.Log
        Try
            Dim config As TsLoggerConfiguration = GetCurrentConfiguration() '// Obtener la configuración.
            If Not IsEnabled(logLevel) Then Return

            '// Obtener la información del archivo de código fuente si está habilitado.
            Dim sourceInfo As String = ""
            If config.IncludeSourceInfo Then
                Dim stackTrace As New Diagnostics.StackTrace(True)
                Dim frame As Diagnostics.StackFrame = stackTrace.GetFrame(1)
                Dim fileName As String = frame.GetFileName()
                Dim method As String = frame.GetMethod().Name
                Dim line As Integer = frame.GetFileLineNumber()
                sourceInfo = $" [Archivo: {fileName}, Método: {method}, Línea: {line}]"
            End If

            '// Mensaje básico sin la fecha y hora.
            Dim basicMessage As String = $"[{eventId.Id,2}: {logLevel,-12}] {_name} - {formatter(state, exception)}{sourceInfo}"
            '// Agregar la fecha y hora actual al inicio del mensaje para consola y archivo.
            Dim timestamp As String = DateTime.Now.ToString("yyyy-MM-dd_HH·mm·sszz")
            Dim messageWithTimestamp As String = $"{timestamp} {basicMessage}"

            '// Registrar en la consola si está habilitado.
            If config.LogToConsole Then
                Console.ForegroundColor = config.LogLevelToColorMap(logLevel)
                Console.WriteLine(messageWithTimestamp)
            End If

            '// Registrar en la salida de depuración si está habilitado.
            If config.LogToDebug Then
                Debug.WriteLine(basicMessage)
            End If

            '// Registrar en un archivo si está habilitado.
            If config.LogToFile Then
                If String.IsNullOrEmpty(config.LogFilePath) Then
                    Debug.WriteLine($"La ruta del archivo de registro no está especificada. Se omite el registro en archivos para el mensaje: {basicMessage}")
                Else
                    Try
                        '// Intentar crear el directorio si no existe.
                        Directory.CreateDirectory(config.LogFilePath)
                        Dim folderPath As String = Path.Combine(config.LogFilePath, DateTime.Now.ToString("yyyy\\MM"))
                        Directory.CreateDirectory(folderPath)

                        Dim fileName As String = $"{DateTime.Now:yyyy-MM-dd_HH·mm·sszz}_{_name}.log"
                        Dim filePath As String = Path.Combine(folderPath, fileName)

                        Using writer As New StreamWriter(filePath, append:=True)
                            writer.WriteLine(messageWithTimestamp)
                        End Using
                    Catch ex As IOException
                        Debug.WriteLine($"Error al intentar crear o acceder a la ruta del archivo de registro '{config.LogFilePath}'. Error: {ex}")
                    End Try
                End If
            End If

            '// Enviar notificación a Pushover si está habilitado.
            If config.LogToPushover AndAlso logLevel >= config.PushoverMinimumLogLevel Then
                Dim parameters As New Dictionary(Of String, String) From {
                    {"token", "a42g7oaz2t4u7unbdg5nm7qaqocht6"},
                    {"user", "uxzAV6NcPoxkAGLSNWSsZX9SfPeUo5"},
                    {"message", basicMessage},
                    {"title", $"{_name}"}, '// Utilizar el nombre del registrador como título.
                    {"priority", GetPushoverPriority(logLevel)} '// Establecer la prioridad en función del nivel de registro.
                }
                If config.NetworkInSeparateThread Then
                    Task.Run(Sub()
                                 Using client As New HttpClient()
                                     Dim response = client.PostAsync("https://api.pushover.net/1/messages.json", New FormUrlEncodedContent(parameters)).Result
                                     Debug.WriteLine(response.ToString)
                                 End Using
                             End Sub)
                Else
                    Using client As New HttpClient()
                        Dim response = client.PostAsync("https://api.pushover.net/1/messages.json", New FormUrlEncodedContent(parameters)).Result
                        Debug.WriteLine(response.ToString)
                    End Using
                End If
            End If

            '// Enviar notificación a Slack si está habilitado.
            If config.LogToSlack Then
                If config.NetworkInSeparateThread Then
                    Task.Run(Sub()
                                 tsl5.Utilidades.EnviarNotificacionSlack(basicMessage, otroTexto:=timestamp, descripcionRemitente:=$"TsLogger {_name}", destinatario:=config.SlackDestination)
                             End Sub)
                Else
                    tsl5.Utilidades.EnviarNotificacionSlack(basicMessage, otroTexto:=timestamp, descripcionRemitente:=$"TsLogger {_name}", destinatario:=config.SlackDestination)
                End If
            End If
        Catch ex As Exception
            Debug.WriteLine($"Excepción en TsLogger: {ex}")
        End Try
    End Sub

    '// Implementación básica de BeginScope (no se necesita un manejo detallado de alcance en esta implementación).
    Private Function ILogger_BeginScope(Of TState)(state As TState) As IDisposable Implements ILogger.BeginScope
        Return New EmptyDisposable()
    End Function

    '// Clase auxiliar para cumplir con la interfaz IDisposable requerida por BeginScope.
    Private Class EmptyDisposable
        Implements IDisposable
        Public Sub Dispose() Implements IDisposable.Dispose
            '// No se requiere ninguna acción aquí.
        End Sub
    End Class
    ' Método auxiliar para convertir el nivel de registro en una prioridad para Pushover.
    Private Function GetPushoverPriority(logLevel As LogLevel) As String
        Select Case logLevel
            Case LogLevel.Critical
                Return "1" ' Prioridad alta
            Case LogLevel.Error
                Return "1" ' Prioridad alta
            Case LogLevel.Warning
                Return "0" ' Prioridad normal
            Case LogLevel.Information
                Return "-1" ' Prioridad baja
            Case LogLevel.Debug
                Return "-1" ' Prioridad baja
            Case LogLevel.Trace
                Return "-2" ' Prioridad mínima
            Case Else
                Return "0" ' Prioridad normal
        End Select
    End Function
End Class

