import Foundation
import Logging
import XCTestDynamicOverlay

public struct MessengerLogger {
  public struct Log: Equatable {
    public init(level: Logger.Level, message: String) {
      self.level = level
      self.message = message
    }

    public var level: Logger.Level
    public var message: String
  }

  public var run: (Log, String, String, UInt) -> Void

  public func callAsFunction(
    _ item: Log,
    file: String = #fileID,
    function: String = #function,
    line: UInt = #line
  ) {
    run(item, file, function, line)
  }
}

extension MessengerLogger {
  public static func live(
    logger: Logger = Logger(label: "xx.network.MessengerClient")
  ) -> MessengerLogger {
    MessengerLogger { item, file, function, line in
      logger.log(
        level: item.level,
        .init(stringLiteral: item.message),
        file: file,
        function: function,
        line: line
      )
    }
  }
}

extension MessengerLogger {
  public static let unimplemented = MessengerLogger(
    run: XCTUnimplemented("\(Self.self)")
  )
}

extension MessengerLogger.Log {
  static func parse(_ string: String) -> MessengerLogger.Log {
    let level: Logger.Level
    let message: String
    let pattern = #"^([A-Z]+)( \d{4}/\d{2}/\d{2})?( \d{1,2}:\d{2}:\d{2}\.\d+)? (.*)"#
    let regex = try! NSRegularExpression(
      pattern: pattern,
      options: .dotMatchesLineSeparators
    )
    let stringRange = NSRange(location: 0, length: string.utf16.count)
    if let match = regex.firstMatch(in: string, range: stringRange) {
      var groups: [Int: String] = [:]
      for rangeIndex in 1..<match.numberOfRanges {
        let nsRange = match.range(at: rangeIndex)
        if !NSEqualRanges(nsRange, NSMakeRange(NSNotFound, 0)) {
          let group = (string as NSString).substring(with: nsRange)
          groups[rangeIndex] = group
        }
      }
      level = MessengerLogger.Log.level(form: groups[1])
      message = groups[4] ?? string
    } else {
      level = .notice
      message = string
    }
    return MessengerLogger.Log(level: level, message: message)
  }

  static func level(form string: String?) -> Logger.Level {
    switch string {
    case "TRACE": return .trace
    case "DEBUG": return .debug
    case "INFO": return .info
    case "WARN": return .warning
    case "ERROR": return .error
    case "CRITICAL": return .critical
    case "FATAL": return .critical
    default: return .notice
    }
  }
}