package com.airlock.iam.apipolicyservice.infrastructure.log;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.model.logs.CorrelationIdSettings;
import com.airlock.iam.common.domain.service.session.IdentityStringGenerator;
import com.airlock.iam.core.misc.util.IamLogUtil;
import com.airlock.iam.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogContext;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import java.util.regex.Pattern;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;

public final class ApiPolicyServiceLogContextInitializer {
   private static final ELogger LOG = ELoggerFactory.getLogger(ApiPolicyServiceLogContextInitializer.class);
   @NonNull
   private final HttpServletRequest request;
   private final Optional<CorrelationIdSettings> correlationIdSettings;

   public static ApiPolicyServiceLogContextInitializer from(HttpServletRequest request, Optional<CorrelationIdSettings> correlationIdSettings) {
      return new ApiPolicyServiceLogContextInitializer(request, correlationIdSettings);
   }

   public ApiPolicyServiceLogContextInitializer ensureInitialized() {
      if (this.isAlreadyInitialized()) {
         return this;
      }

      this.markAsInitialized();
      ELogContext logContext = ELoggerFactory.getLogContext();
      IamLogUtil.initializeLogContext(logContext);
      this.extractWafInfos();
      return this;
   }

   private boolean isAlreadyInitialized() {
      Boolean already = (Boolean)this.request.getAttribute("IAM_LOG_CONTEXT_ALREADY_INITIALIZED");
      return already != null && already;
   }

   private void markAsInitialized() {
      this.request.setAttribute("IAM_LOG_CONTEXT_ALREADY_INITIALIZED", Boolean.TRUE);
   }

   public void updateConfigurationContext(ConfigurationContext configurationContext) {
      ELoggerFactory.getLogContext().put("CONFIG_CONTEXT", configurationContext.toString());
   }

   private void extractWafInfos() {
      String reqId = this.request.getHeader("X-AL-RequestId");
      if (StringUtils.isEmpty(reqId)) {
         reqId = IdentityStringGenerator.generateIdentityString();
      }

      ELoggerFactory.getLogContext().put("REQUEST_ID", reqId);
      String sessId = this.request.getHeader("X-AL-SessionId");
      if (StringUtils.isEmpty(sessId)) {
         sessId = IdentityStringGenerator.generateIdentityString();
      }

      ELoggerFactory.getLogContext().put("GSID", sessId);
      if (this.correlationIdSettings.isPresent()) {
         this.storeCorrelationIdInLogContext(this.correlationIdSettings.get());
      } else {
         ELoggerFactory.getLogContext().remove("CORRELATION_ID");
      }

      String remoteAddr = this.request.getHeader("X-AL-RemoteAddr");
      if (StringUtils.isEmpty(remoteAddr)) {
         remoteAddr = this.request.getRemoteAddr();
      }

      IamLogUtil.addRequestLogData(this.request, "clientIp", remoteAddr);
      ELoggerFactory.getLogContext().put("CLIENT_IP", remoteAddr);
   }

   private void storeCorrelationIdInLogContext(CorrelationIdSettings settings) {
      String corrId = this.request.getHeader(settings.headerName());
      if (!StringUtils.isEmpty(corrId)) {
         if (isValidCorrelationId(corrId, settings.validationPattern())) {
            LOG.debug("Extracted correlation ID " + LogUtil.quote(corrId) + " from request header " + LogUtil.quote(settings.headerName()));
            ELoggerFactory.getLogContext().put("CORRELATION_ID", corrId);
         } else {
            LOG.warn(
               EDefaultLogType.GENERIC_LOG_TYPE,
               "Invalid correlation ID " + LogUtil.logSafeQuote(corrId) + " in header " + LogUtil.quote(settings.headerName()) + "; ignoring"
            );
            ELoggerFactory.getLogContext().remove("CORRELATION_ID");
         }
      } else {
         LOG.debug("Did not find expected correlation ID header " + LogUtil.quote(settings.headerName()) + " in request.");
         ELoggerFactory.getLogContext().remove("CORRELATION_ID");
      }
   }

   private static boolean isValidCorrelationId(String corrId, Pattern validationPattern) {
      return validationPattern.matcher(corrId).matches();
   }

   @SuppressFBWarnings(justification = "generated code")
   private ApiPolicyServiceLogContextInitializer(@NonNull HttpServletRequest request, Optional<CorrelationIdSettings> correlationIdSettings) {
      if (request == null) {
         throw new NullPointerException("request is marked non-null but is null");
      }

      this.request = request;
      this.correlationIdSettings = correlationIdSettings;
   }
}
