/*
 * This file is part of LibEuFin.
 * Copyright (C) 2025 Taler Systems S.A.

 * LibEuFin is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3, or
 * (at your option) any later version.

 * LibEuFin is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
 * Public License for more details.

 * You should have received a copy of the GNU Affero General Public
 * License along with LibEuFin; see the file COPYING.  If not, see
 * <http://www.gnu.org/licenses/>
 */

package tech.libeufin.common

import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.event.Level;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.ILoggerFactory;
import org.slf4j.IMarkerFactory;
import org.slf4j.helpers.LegacyAbstractLogger;
import org.slf4j.helpers.MessageFormatter;
import org.slf4j.helpers.BasicMarkerFactory;
import org.slf4j.helpers.BasicMDCAdapter;
import org.slf4j.spi.MDCAdapter;
import org.slf4j.spi.SLF4JServiceProvider;
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.io.PrintStream


class TalerLogger(private val loggerName: String): LegacyAbstractLogger() {
    private fun isLevelEnabled(level: Level): Boolean = level.toInt() >= TalerServiceProvider.currentLevel.toInt()
    override fun isTraceEnabled(): Boolean = isLevelEnabled(Level.TRACE)
    override fun isDebugEnabled(): Boolean = isLevelEnabled(Level.DEBUG)
    override fun isInfoEnabled(): Boolean = isLevelEnabled(Level.INFO)
    override fun isWarnEnabled(): Boolean = isLevelEnabled(Level.WARN)
    override fun isErrorEnabled(): Boolean = isLevelEnabled(Level.ERROR)

    override fun getFullyQualifiedCallerName(): String = loggerName

    override fun handleNormalizedLoggingCall(level: Level, marker: Marker?, messagePattern: String?, arguments: Array<Any>?, throwable: Throwable?) {
        val name = fullyQualifiedCallerName;
        if (
            !isLevelEnabled(level) ||
            (name.startsWith("io.ktor") && level.toInt() < Level.WARN.toInt()) || 
            name.startsWith("com.zaxxer.hikari")
        ) return
        val callId = org.slf4j.MDC.get("call-id")
        val logEntry = buildString {
            if (timestampFmt != null) {
                append(LocalDateTime.now().format(timestampFmt))
                append(' ')
            }
            if (callId != null) {
                append(callId)
                append(' ')
            }
            append(level.name.padEnd(5))
            append(' ')
            append(name)
            append(" - ")
            append(MessageFormatter.basicArrayFormat(messagePattern, arguments))
            
            throwable?.let { t ->
                append("${t.javaClass.simpleName}: ${t.message}")
                t.stackTrace.take(10).forEach { stackElement ->
                    append("\n\tat $stackElement")
                }
            }
        }
        
        System.err.println(logEntry)
    }

    companion object {
        // We skip logging timestamp if systemd is used
        private val skipTimestamp = System.getenv("JOURNAL_STREAM") != null
        // A null timestamp formatter mean we should skip it
        private val timestampFmt = if (skipTimestamp) null else DateTimeFormatter.ofPattern("dd-MMM-yyyy'T'HH:mm:ss.SSS")
    }
}

class TalerServiceProvider: SLF4JServiceProvider {
    private val markerFactory = BasicMarkerFactory()
    private val mdcAdapter = BasicMDCAdapter()

    override fun getLoggerFactory() = TalerServiceProvider
    override fun getMarkerFactory() = markerFactory
    override fun getMDCAdapter() = mdcAdapter
    override fun getRequestedApiVersion() = "2.0.99"
    override fun initialize() {}

    companion object: ILoggerFactory {
        var currentLevel = Level.TRACE
        private val loggerMap: ConcurrentMap<String, TalerLogger> = ConcurrentHashMap() 

        override fun getLogger(name: String): Logger 
            = loggerMap.computeIfAbsent(name, ::TalerLogger)
    }
}