//
// Copyright 2021 The Matrix.org Foundation C.I.C
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import MatrixRustSDK

/**
 Logging utility that provies multiple logging levels as well as file output and rolling.
 Its purpose is to provide a common entry for customizing logging and should be used throughout the code.
 */
enum MXLog {
    private enum Constants {
        static let target = "elementx"
    }
    
    // Rust side crashes if invoking setupTracing multiple times
    private static var didConfigureOnce = false
    
    private static var rootSpan: Span!
    private static var target: String!
    
    static func configure(target: String? = nil,
                          logLevel: TracingConfiguration.LogLevel,
                          otlpConfiguration: OTLPConfiguration? = nil) {
        guard !didConfigureOnce else { return }
        
        RustTracing.setup(configuration: .init(logLevel: logLevel, target: target), otlpConfiguration: otlpConfiguration)
        
        if let target {
            self.target = target
        } else {
            self.target = Constants.target
        }
        
        rootSpan = Span(file: #file, line: #line, level: .info, target: self.target, name: "root")
        rootSpan.enter()
        
        didConfigureOnce = true
    }
    
    static func createSpan(_ name: String,
                           file: String = #file,
                           function: String = #function,
                           line: Int = #line,
                           column: Int = #column) -> Span {
        createSpan(name, level: .info, file: file, function: function, line: line, column: column)
    }
    
    static func verbose(_ message: Any,
                        file: String = #file,
                        function: String = #function,
                        line: Int = #line,
                        column: Int = #column,
                        context: Any? = nil) {
        log(message, level: .trace, file: file, function: function, line: line, column: column, context: context)
    }
    
    static func debug(_ message: Any,
                      file: String = #file,
                      function: String = #function,
                      line: Int = #line,
                      column: Int = #column,
                      context: Any? = nil) {
        log(message, level: .debug, file: file, function: function, line: line, column: column, context: context)
    }
    
    static func info(_ message: Any,
                     file: String = #file,
                     function: String = #function,
                     line: Int = #line,
                     column: Int = #column,
                     context: Any? = nil) {
        log(message, level: .info, file: file, function: function, line: line, column: column, context: context)
    }
    
    static func warning(_ message: Any,
                        file: String = #file,
                        function: String = #function,
                        line: Int = #line,
                        column: Int = #column,
                        context: Any? = nil) {
        log(message, level: .warn, file: file, function: function, line: line, column: column, context: context)
    }
    
    /// Log error with additional details
    ///
    /// - Parameters:
    ///     - message: Description of the error without any variables (this is to improve error aggregations by type)
    ///     - context: Additional context-dependent details about the issue
    static func error(_ message: Any,
                      file: String = #file,
                      function: String = #function,
                      line: Int = #line,
                      column: Int = #column,
                      context: Any? = nil) {
        log(message, level: .error, file: file, function: function, line: line, column: column, context: context)
    }
    
    /// Log failure with additional details
    ///
    /// A failure is any type of programming error which should never occur in production. In `DEBUG` configuration
    /// any failure will raise `assertionFailure`
    ///
    /// - Parameters:
    ///     - message: Description of the error without any variables (this is to improve error aggregations by type)
    ///     - context: Additional context-dependent details about the issue
    static func failure(_ message: Any,
                        file: String = #file,
                        function: String = #function,
                        line: Int = #line,
                        column: Int = #column,
                        context: Any? = nil) {
        log(message, level: .error, file: file, function: function, line: line, column: column, context: context)
        
        #if DEBUG
        assertionFailure("\(message)")
        #endif
    }
    
    // MARK: - Private
    
    // periphery:ignore:parameters function,column
    private static func createSpan(_ name: String,
                                   level: LogLevel,
                                   file: String = #file,
                                   function: String = #function,
                                   line: Int = #line,
                                   column: Int = #column) -> Span {
        guard didConfigureOnce else {
            fatalError()
        }
        
        if Span.current().isNone() {
            rootSpan.enter()
        }
        
        return Span(file: file, line: UInt32(line), level: level, target: target, name: name)
    }
    
    // periphery:ignore:parameters function,column,context
    private static func log(_ message: Any,
                            level: LogLevel,
                            file: String = #file,
                            function: String = #function,
                            line: Int = #line,
                            column: Int = #column,
                            context: Any? = nil) {
        guard didConfigureOnce else {
            return
        }
        
        if Span.current().isNone() {
            rootSpan.enter()
        }
        
        logEvent(file: (file as NSString).lastPathComponent, line: UInt32(line), level: level, target: target, message: "\(message)")
    }
}
