package com.airlock.iam.login.domain.model.idpropagation;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.common.api.domain.model.valueprovider.ValueProvider;
import com.airlock.iam.core.misc.util.ticket.KeyMultiValue;
import com.airlock.iam.core.misc.util.ticket.Ticket;
import com.airlock.iam.core.misc.util.ticket.TicketEncoder;
import com.airlock.iam.core.misc.util.ticket.TicketEncodingException;
import com.airlock.iam.flow.shared.api.domain.model.valueprovider.ValueMapProvider;
import com.airlock.iam.flow.shared.application.configuration.TicketKeyValue;
import com.airlock.iam.flow.shared.application.configuration.TicketStringProviderConfig;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import java.nio.charset.Charset;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;

@LogicForConfig(TicketStringProviderConfig.class)
public final class TicketStringProvider implements ValueProvider<String> {
   private final ValueMapProvider valuesProvider;
   private final List<TicketKeyValue> keyValues;
   private final Duration validity;
   private final Charset encodingCharset;
   private final TicketEncoder encoder;

   @Inject
   public TicketStringProvider(
      ValueMapProvider valuesProvider, List<TicketKeyValue> keyValues, Duration validity, Charset encodingCharset, TicketEncoder encoder
   ) {
      this.valuesProvider = valuesProvider;
      this.keyValues = keyValues;
      this.validity = validity;
      this.encodingCharset = encodingCharset;
      this.encoder = encoder;
   }

   @Nullable
   public String getValue() {
      Map<String, Object> values = this.valuesProvider.provideValueMap();
      Ticket.TicketBuilder ticket = Ticket.builder().expiryDate(Instant.now().plus(this.validity));
      this.keyValues.stream().map(kv -> createKeyMultiValue(kv, values)).filter(Optional::isPresent).map(Optional::get).forEach(ticket::keyMultiValue);
      return this.encodeTicket(ticket.build());
   }

   private String encodeTicket(Ticket ticket) {
      try {
         return this.encoder.encode(ticket, this.encodingCharset.name());
      } catch (TicketEncodingException e) {
         throw new FailureException(e);
      }
   }

   private static Optional<KeyMultiValue> createKeyMultiValue(TicketKeyValue config, Map<String, Object> values) {
      Object valueObject = values.get(config.getValueSelector());
      if (valueObject instanceof List<?> objectList) {
         if (config.isOmitIfEmpty() && objectList.isEmpty()) {
            return Optional.empty();
         }

         List<String> stringList = objectList.stream().map(objectElement -> Objects.toString(objectElement, "")).collect(Collectors.toList());
         return Optional.of(KeyMultiValue.kmv(config.getTicketKey(), stringList.toArray(String[]::new)));
      } else {
         String value = Objects.toString(valueObject, "");
         return config.isOmitIfEmpty() && StringUtils.isEmpty(value) ? Optional.empty() : Optional.of(KeyMultiValue.kmv(config.getTicketKey(), value));
      }
   }
}
