package com.airlock.iam.common.infrastructure.request;

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.domain.service.uri.UrlUtils;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.model.gateway.WafSettings;
import com.airlock.iam.common.domain.model.request.ClientIp;
import com.airlock.iam.common.domain.model.request.ClientTlsCertificate;
import com.airlock.iam.common.domain.model.request.RequestId;
import com.airlock.iam.common.domain.model.request.RequestUrl;
import com.airlock.iam.common.domain.model.request.VirtualHostName;
import com.airlock.iam.common.domain.model.session.RemovedRole;
import com.airlock.iam.common.domain.model.session.SessionId;
import com.airlock.iam.common.domain.model.session.WafClientFingerprintingNotification;
import com.airlock.iam.common.domain.model.session.WafClientFingerprintingTotalPenaltyPoints;
import com.airlock.iam.common.domain.model.session.WafMlAnomaly;
import com.airlock.iam.common.domain.model.session.WafSessionTerminationReason;
import com.airlock.iam.common.domain.service.request.AirlockEnvCookiesInformationService;
import com.airlock.iam.core.misc.util.airlock.AirlockEnvCookie;
import com.airlock.iam.core.misc.util.airlock.AirlockWAFEnvValues;
import com.airlock.iam.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.security.cert.CertificateException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collections;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;

public class AirlockEnvCookiesInformationServiceImpl implements AirlockEnvCookiesInformationService {
   private static final ELogger LOG = ELoggerFactory.getLogger(AirlockEnvCookiesInformationServiceImpl.class);
   private static final String SESSION_TERMINATION_REASON = "SESSION_TERMINATION_REASON";
   private static final String ML_ANOMALY = "ML_ANOMALY";
   private static final String CFP_TOTAL_PENALTY_POINTS = "CFP_TOTAL_PENALTY_POINTS";
   private static final String CFP_NOTIFICATION = "CFP_NOTIFICATION";
   private static final String REMOVED_ROLES = "REMOVED_ROLES";
   private final Provider<Optional<WafSettings>> wafConfig;
   private final Provider<HttpServletRequest> request;

   @Inject
   public AirlockEnvCookiesInformationServiceImpl(Provider<Optional<WafSettings>> wafConfig, Provider<HttpServletRequest> request) {
      this.wafConfig = wafConfig;
      this.request = request;
   }

   @Override
   public Optional<WafSessionTerminationReason> getWafSessionTerminationReason() {
      return this.getDecodedEnvironmentCookieValues("SESSION_TERMINATION_REASON").flatMap(WafSessionTerminationReason::parseReason);
   }

   @Override
   public Optional<WafMlAnomaly> getWafMlAnomaly() {
      return this.getDecodedEnvironmentCookieValues("ML_ANOMALY").map(AirlockEnvCookiesInformationServiceImpl::parseMlAnomaly);
   }

   private static WafMlAnomaly parseMlAnomaly(String anomalyState) {
      try {
         return WafMlAnomaly.parseAnomaly(anomalyState);
      } catch (NoSuchElementException e) {
         throw new ViolationException(String.format("Invalid client fingerprinting notification %s", LogUtil.logSafeQuote(anomalyState)), e);
      }
   }

   @Override
   public Optional<WafClientFingerprintingTotalPenaltyPoints> getWafCfpTotalPenaltyPoints() {
      return this.getDecodedEnvironmentCookieValues("CFP_TOTAL_PENALTY_POINTS")
         .map(AirlockEnvCookiesInformationServiceImpl::parsePenaltyPoints)
         .map(WafClientFingerprintingTotalPenaltyPoints::new);
   }

   private static int parsePenaltyPoints(String penaltyPoints) {
      try {
         return Integer.parseInt(penaltyPoints);
      } catch (NumberFormatException e) {
         throw new ViolationException(String.format("Invalid client fingerprinting score %s", LogUtil.logSafeQuote(penaltyPoints)), e);
      }
   }

   @Override
   public Set<RemovedRole> getRemovedRoles() {
      Optional<String> value = this.getDecodedEnvironmentCookieValues("REMOVED_ROLES");
      return !value.isEmpty() && !Strings.CI.equals(value.get(), "none")
         ? Arrays.stream(value.get().split(",")).map(RemovedRole::parse).collect(Collectors.toSet())
         : Collections.emptySet();
   }

   @Override
   public Optional<VirtualHostName> getVirtualHostName() {
      String virtualHostName = AirlockEnvCookie.getVirtualHostName((HttpServletRequest)this.request.get(), this.getCookiePrefix());
      return Optional.ofNullable(virtualHostName).map(VirtualHostName::new);
   }

   @Override
   public Optional<WafClientFingerprintingNotification> getWafCfpNotification() {
      return this.getDecodedEnvironmentCookieValues("CFP_NOTIFICATION").map(Boolean::parseBoolean).map(WafClientFingerprintingNotification::new);
   }

   private Optional<String> getDecodedEnvironmentCookieValues(String cookieName) {
      return this.getEncodedEnvironmentCookie(cookieName).map(Cookie::getValue).<String>map(StringUtils::trimToNull).map(UrlUtils::urlDecode);
   }

   private Optional<Cookie> getEncodedEnvironmentCookie(String cookieName) {
      return Arrays.stream(((HttpServletRequest)this.request.get()).getCookies())
         .filter(cookie -> cookie.getName().equalsIgnoreCase(this.getCookiePrefix() + cookieName))
         .findFirst();
   }

   @Override
   public Optional<ClientIp> getClientIp() {
      String cookieClientIp = AirlockEnvCookie.getRemoteAddress((HttpServletRequest)this.request.get(), this.getCookiePrefix());
      if (StringUtils.isBlank(cookieClientIp)) {
         LOG.info("Cannot read client IP from Airlock Environment Cookie. Are Gateway settings configured without using a Gateway?");
      }

      return Optional.ofNullable(cookieClientIp).map(ClientIp::new);
   }

   @Override
   public Optional<RequestId> getRequestId() {
      String cookieRequestId = AirlockEnvCookie.getRequestId((HttpServletRequest)this.request.get(), this.getCookiePrefix());
      return Optional.ofNullable(cookieRequestId).filter(AirlockEnvCookiesInformationServiceImpl::validateRequestID).map(RequestId::new);
   }

   @Override
   public Optional<SessionId> getSessionId() {
      String cookieSessionId = AirlockEnvCookie.getSessionId((HttpServletRequest)this.request.get(), this.getCookiePrefix());
      return Optional.ofNullable(cookieSessionId).filter(AirlockEnvCookiesInformationServiceImpl::validateSessionId).map(SessionId::new);
   }

   @Override
   public Optional<RequestUrl> getRequestUrl() {
      try {
         AirlockWAFEnvValues envValues = new AirlockWAFEnvValues(((HttpServletRequest)this.request.get()).getCookies(), this.getCookiePrefix());
         return envValues.getUrlFromAirlockEnvironmentCookie().map(toRequestUrl());
      } catch (URISyntaxException | MalformedURLException e) {
         throw new ViolationException("invalid URL from WAF environment cookie", e);
      }
   }

   @Override
   public Optional<ClientTlsCertificate> getMtlsClientCertificate() {
      try {
         String cookiePrefix = this.getCookiePrefix();
         LOG.debug(String.format("Trying to extract mTLS client certificate from cookie %s.", LogUtil.quote(cookiePrefix + "SSL_CLIENT_CERT")));
         AirlockWAFEnvValues envValues = new AirlockWAFEnvValues(((HttpServletRequest)this.request.get()).getCookies(), cookiePrefix);
         return Optional.ofNullable(envValues.getClientCertificate()).map(ClientTlsCertificate::validClientTlsCertificate);
      } catch (CertificateException | ParseException e) {
         return Optional.of(ClientTlsCertificate.invalidClientTlsCertificate(e.getMessage()));
      }
   }

   private static boolean validateRequestID(String requestId) {
      if (!RequestId.isValidRequestId(requestId)) {
         LOG.warn(EDefaultLogType.GENERIC_LOG_TYPE, "Invalid request id in Airlock Environment Cookie; ignoring");
         return false;
      } else {
         return true;
      }
   }

   private static boolean validateSessionId(String sessionId) {
      if (!SessionId.isValidSessionId(sessionId)) {
         LOG.warn(EDefaultLogType.GENERIC_LOG_TYPE, "Invalid session id in Airlock Environment Cookie; ignoring");
         return false;
      } else {
         return true;
      }
   }

   private static Function<String, RequestUrl> toRequestUrl() {
      return requestUrl -> {
         try {
            return new RequestUrl(requestUrl);
         } catch (URISyntaxException | MalformedURLException e) {
            throw new ViolationException("invalid URL from WAF environment cookie", e);
         }
      };
   }

   private String getCookiePrefix() {
      return ((Optional)this.wafConfig.get()).map(WafSettings::getEnvironmentCookiePrefix).orElse("AL_ENV_");
   }
}
