package com.airlock.iam.common.domain.service.externalization;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.common.domain.model.auth.ExternalizationContext;
import com.airlock.iam.common.domain.service.jwt.DecryptingJwtTicketDecoder;
import com.airlock.iam.common.domain.service.jwt.EncryptingJwtTicketEncoder;
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.TicketEncodingException;
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.inject.Inject;
import jakarta.inject.Provider;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.Map.Entry;

public class ExternalizationService {
   private static final ELogger LOG = ELoggerFactory.getLogger(ExternalizationService.class);
   private static final String ENCODING_SCHEME = StandardCharsets.UTF_8.name();
   public static final String USERID_KEY = "username";
   public static final String CONFIGURATION_CONTEXT_KEY = "configuration-context";
   private EncryptingJwtTicketEncoder encoder;
   private DecryptingJwtTicketDecoder decoder;
   private Provider<ConfigurationContext> configurationContext;

   @Inject
   public ExternalizationService(EncryptingJwtTicketEncoder encoder, DecryptingJwtTicketDecoder decoder, Provider<ConfigurationContext> configurationContext) {
      this.encoder = encoder;
      this.decoder = decoder;
      this.configurationContext = configurationContext;
   }

   public Optional<ExternalizationContext> create(ExternalizationContext.ExternalizationContextContent content) {
      Ticket ticket = this.toTicket(content);

      try {
         String jwt = this.encoder.encode(ticket, ENCODING_SCHEME);
         LOG.debug("created encrypted JWT ticket for externalization.");
         return Optional.of(ExternalizationContext.builder().jwt(jwt).build());
      } catch (TicketEncodingException e) {
         LOG.debug("failed to create and encode JWT ticket for externalization", e);
         return Optional.empty();
      }
   }

   private Ticket toTicket(ExternalizationContext.ExternalizationContextContent content) {
      Ticket ticket = new Ticket();
      ticket.setExpiryDate(content.getExpirationDate().toInstant());
      ticket.addKeyMultiValue(new KeyMultiValue("username", content.getUserId()));
      ticket.addKeyMultiValue(new KeyMultiValue("configuration-context", ((ConfigurationContext)this.configurationContext.get()).toString()));

      for (Entry<String, String> entry : content.getValues().entrySet()) {
         ticket.addKeyMultiValue(new KeyMultiValue(entry.getKey(), entry.getValue()));
      }

      return ticket;
   }

   public Optional<ExternalizationContext.ExternalizationContextContent> verify(ExternalizationContext context) {
      try {
         Ticket ticket = this.decoder.decode(context.getJwt(), ENCODING_SCHEME);
         LOG.debug("verified JWT ticket from externalization.");
         ExternalizationContext.ExternalizationContextContent content = this.fromTicket(ticket);
         return Optional.of(content);
      } catch (TicketEncodingException e) {
         LOG.info("failed to verify and decode JWT ticket from externalization: " + e.getMessage());
         return Optional.empty();
      }
   }

   private ExternalizationContext.ExternalizationContextContent fromTicket(Ticket ticket) {
      ExternalizationContext.ExternalizationContextContent.ExternalizationContextContentBuilder contentBuilder = ExternalizationContext.ExternalizationContextContent.builder();
      ConfigurationContext storedContext = ConfigurationContext.configurationContext(ticket.getFirstValueForName("configuration-context").get());
      if (!this.configurationContextMatches(storedContext)) {
         throw new FailureException(
            String.format(
               "Stored configuration context '%s' does not match current configuration context '%s'. Aborting externalization context restore.",
               storedContext,
               ((ConfigurationContext)this.configurationContext.get()).getIdentifier()
            )
         );
      }

      contentBuilder.userId(ticket.getFirstValueForName("username").get());
      contentBuilder.expirationDate(ticket.getExpiryDate());

      for (Entry<String, KeyMultiValue> entry : ticket.entrySet()) {
         contentBuilder.value(entry.getKey(), entry.getValue().getFirstValue());
      }

      return contentBuilder.build();
   }

   private boolean configurationContextMatches(ConfigurationContext storedContext) {
      return ((ConfigurationContext)this.configurationContext.get()).equals(storedContext);
   }

   @SuppressFBWarnings(justification = "generated code")
   public static ExternalizationService.ExternalizationServiceBuilder builder() {
      return new ExternalizationService.ExternalizationServiceBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class ExternalizationServiceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private EncryptingJwtTicketEncoder encoder;
      @SuppressFBWarnings(justification = "generated code")
      private DecryptingJwtTicketDecoder decoder;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<ConfigurationContext> configurationContext;

      @SuppressFBWarnings(justification = "generated code")
      ExternalizationServiceBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public ExternalizationService.ExternalizationServiceBuilder encoder(EncryptingJwtTicketEncoder encoder) {
         this.encoder = encoder;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ExternalizationService.ExternalizationServiceBuilder decoder(DecryptingJwtTicketDecoder decoder) {
         this.decoder = decoder;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ExternalizationService.ExternalizationServiceBuilder configurationContext(Provider<ConfigurationContext> configurationContext) {
         this.configurationContext = configurationContext;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ExternalizationService build() {
         return new ExternalizationService(this.encoder, this.decoder, this.configurationContext);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "ExternalizationService.ExternalizationServiceBuilder(encoder="
            + this.encoder
            + ", decoder="
            + this.decoder
            + ", configurationContext="
            + this.configurationContext
            + ")";
      }
   }
}
