/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.instrumentation.log4j.appender.v2_17;

import java.util.Collections;
import java.util.Map;
import javax.annotation.Nullable;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.impl.ThrowableProxy;
import org.apache.logging.log4j.core.time.Instant;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.StringMapMessage;
import org.apache.logging.log4j.message.StructuredDataMessage;
import org.apache.logging.log4j.util.ReadOnlyStringMap;

class LogEventToReplay implements LogEvent {

  private static final long serialVersionUID = 1L;

  // Log4j 2 reuses LogEvent object, so we make a copy of all the fields that are used during export
  // in order to be able to replay the log event later.

  private final String loggerName;
  private final Message message;
  private final Level level;
  private final Instant instant;
  private final Throwable thrown;
  private final Marker marker;
  private final ReadOnlyStringMap contextData;
  private final String threadName;
  private final long threadId;

  LogEventToReplay(LogEvent logEvent) {
    this.loggerName = logEvent.getLoggerName();
    Message messageOrigin = logEvent.getMessage();
    if (messageOrigin instanceof StructuredDataMessage) {
      StructuredDataMessage structuredDataMessage = (StructuredDataMessage) messageOrigin;
      this.message =
          // Log4j 2 reuses StructuredDataMessage object
          new StructuredDataMessage(
              structuredDataMessage.getId(),
              structuredDataMessage.getFormat(),
              structuredDataMessage.getType(),
              structuredDataMessage.getData());
    } else if (messageOrigin instanceof StringMapMessage) {
      // StringMapMessage objects are not reused by Log4j 2
      this.message = messageOrigin;
    } else {
      this.message = new MessageCopy(logEvent.getMessage());
    }

    this.level = logEvent.getLevel();
    this.instant = logEvent.getInstant();
    this.thrown = logEvent.getThrown();
    this.marker = logEvent.getMarker();
    this.contextData = logEvent.getContextData();
    this.threadName = logEvent.getThreadName();
    this.threadId = logEvent.getThreadId();
  }

  @Override
  public LogEvent toImmutable() {
    return null;
  }

  @SuppressWarnings("deprecation") // Override
  @Override
  public Map<String, String> getContextMap() {
    return Collections.emptyMap();
  }

  @Override
  public ReadOnlyStringMap getContextData() {
    return contextData;
  }

  @Nullable
  @Override
  public ThreadContext.ContextStack getContextStack() {
    return null;
  }

  @Override
  public String getLoggerFqcn() {
    return null;
  }

  @Override
  public Level getLevel() {
    return level;
  }

  @Override
  public String getLoggerName() {
    return loggerName;
  }

  @Override
  public Marker getMarker() {
    return marker;
  }

  @Override
  public Message getMessage() {
    return message;
  }

  @Override
  public long getTimeMillis() {
    return 0;
  }

  @Override
  public Instant getInstant() {
    return instant;
  }

  @Override
  public StackTraceElement getSource() {
    return null;
  }

  @Override
  public String getThreadName() {
    return threadName;
  }

  @Override
  public long getThreadId() {
    return threadId;
  }

  @Override
  public int getThreadPriority() {
    return 0;
  }

  @Override
  public Throwable getThrown() {
    return thrown;
  }

  @Override
  public ThrowableProxy getThrownProxy() {
    return null;
  }

  @Override
  public boolean isEndOfBatch() {
    return false;
  }

  @Override
  public boolean isIncludeLocation() {
    return false;
  }

  @Override
  public void setEndOfBatch(boolean endOfBatch) {}

  @Override
  public void setIncludeLocation(boolean locationRequired) {}

  @Override
  public long getNanoTime() {
    return 0;
  }

  private static class MessageCopy implements Message {

    private static final long serialVersionUID = 1L;
    private final String formattedMessage;
    private final String format;
    private final Object[] parameters;
    private final Throwable throwable;

    public MessageCopy(Message message) {
      this.formattedMessage = message.getFormattedMessage();
      this.format = message.getFormat();
      this.parameters = message.getParameters();
      this.throwable = message.getThrowable();
    }

    @Override
    public String getFormattedMessage() {
      return formattedMessage;
    }

    @Override
    public String getFormat() {
      return format;
    }

    @Override
    public Object[] getParameters() {
      return parameters;
    }

    @Override
    public Throwable getThrowable() {
      return throwable;
    }
  }
}
