package com.airlock.iam.common.domain.model.session;

import com.airlock.iam.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@SuppressFBWarnings(value = "REDOS", justification = "Match of COOKIE_PATTERN is never against untrusted input and REDOS is only theoretical.")
public abstract sealed class WafSessionTerminationReason
   permits WafSessionTerminationReason.IdleTimeout,
   WafSessionTerminationReason.LifetimeExceeded,
   WafSessionTerminationReason.ThreadHandling,
   WafSessionTerminationReason.ControlAPI,
   WafSessionTerminationReason.ClientFingerprinting {
   private static final ELogger LOGGER = ELoggerFactory.getLogger(WafSessionTerminationReason.class);
   private static final Pattern COOKIE_PATTERN = Pattern.compile("[^:]+(:[^:=]+=[^:=]+)*");

   public static Optional<? extends WafSessionTerminationReason> parseReason(String cookieValue) {
      if (!COOKIE_PATTERN.matcher(cookieValue).matches()) {
         LOGGER.warn(EDefaultLogType.GENERIC_LOG_TYPE, "The cookie value of the session termination reason is not of the correct form: " + cookieValue);
         return Optional.empty();
      }

      String[] parts = cookieValue.split(":");
      String reason = parts[0];
      Map<String, String> additionalArguments = Arrays.stream(parts)
         .skip(1L)
         .map(part -> part.split("="))
         .collect(Collectors.toUnmodifiableMap(element -> (String)element[0], element -> (String)element[1]));

      return switch (reason) {
         case "IdleTimeout" -> Optional.of(new WafSessionTerminationReason.IdleTimeout());
         case "LifetimeExceeded" -> Optional.of(new WafSessionTerminationReason.LifetimeExceeded());
         case "ThreadHandling" -> Optional.of(new WafSessionTerminationReason.ThreadHandling());
         case "ControlAPI" -> Optional.of(new WafSessionTerminationReason.ControlAPI());
         case "ClientFingerprinting" -> WafSessionTerminationReason.ClientFingerprinting.createClientFingerprinting(additionalArguments);
         default -> invalidReason(reason);
      };
   }

   private static Optional<? extends WafSessionTerminationReason> invalidReason(String reason) {
      LOGGER.warn(EDefaultLogType.GENERIC_LOG_TYPE, "Unknown session termination reason: " + reason);
      return Optional.empty();
   }

   public static final class ClientFingerprinting extends WafSessionTerminationReason {
      private final Optional<Integer> totalPenaltyPoints;

      private ClientFingerprinting(Optional<Integer> totalPenaltyPoints) {
         this.totalPenaltyPoints = totalPenaltyPoints;
      }

      public static Optional<WafSessionTerminationReason.ClientFingerprinting> createClientFingerprinting(Map<String, String> additionalArguments) {
         String value = additionalArguments.get("TOTAL_PENALTY_POINTS");

         try {
            return value == null
               ? Optional.of(new WafSessionTerminationReason.ClientFingerprinting(Optional.empty()))
               : Optional.of(new WafSessionTerminationReason.ClientFingerprinting(Optional.of(Integer.parseInt(value))));
         } catch (NumberFormatException e) {
            WafSessionTerminationReason.LOGGER
               .warn(EDefaultLogType.GENERIC_LOG_TYPE, "Invalid value for total penalty points in client fingerprinting: " + value);
            return Optional.empty();
         }
      }

      public Optional<Integer> totalPenaltyPoints() {
         return this.totalPenaltyPoints;
      }
   }

   public static final class ControlAPI extends WafSessionTerminationReason {
   }

   public static final class IdleTimeout extends WafSessionTerminationReason {
   }

   public static final class LifetimeExceeded extends WafSessionTerminationReason {
   }

   public static final class ThreadHandling extends WafSessionTerminationReason {
   }
}
