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

import com.airlock.iam.base.misc.time.RealTimeProvider;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.common.domain.model.jwt.JwtKeys;
import com.airlock.iam.core.domain.model.jwt.JweToken;
import com.airlock.iam.core.domain.model.jwt.Plaintext;
import com.airlock.iam.core.domain.service.jwt.JwtHmacAndAesGcmEncryption;
import com.airlock.iam.core.misc.util.ticket.Ticket;
import com.airlock.iam.core.misc.util.ticket.TicketEncodingException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;

public final class JwtHmacAndAesGcmEncryptionImpl implements JwtHmacAndAesGcmEncryption {
   private static final String VALUE_TICKET_KEY = "value";
   private final TimeProvider timeProvider;
   private final EncryptingJwtTicketEncoder encoder;
   private final DecryptingJwtTicketDecoder decoder;
   private final Optional<Duration> jwtLifetime;

   private JwtHmacAndAesGcmEncryptionImpl(JwtKeys keys, TimeProvider timeProvider, Optional<Duration> jwtLifetime) {
      this.timeProvider = timeProvider;
      this.jwtLifetime = jwtLifetime;
      EncryptingJwtTicketEncoderImpl.EncryptingJwtTicketEncoderImplBuilder encoderBuilder = EncryptingJwtTicketEncoderImpl.builder(keys)
         .timeProvider(timeProvider)
         .usernameTicketKey(Optional.empty());
      DecryptingJwtTicketDecoderImpl.DecryptingJwtTicketDecoderImplBuilder decoderBuilder = DecryptingJwtTicketDecoderImpl.builder(keys)
         .timeProvider(timeProvider)
         .isSubjectMandatory(false)
         .issuerIsMandatory(false);
      if (jwtLifetime.isPresent()) {
         encoderBuilder.useTicketExpiryDate(true);
         decoderBuilder.expirationTimeIsMandatory(true);
      } else {
         encoderBuilder.useTicketExpiryDate(false);
         decoderBuilder.expirationTimeIsMandatory(false);
      }

      this.encoder = encoderBuilder.build();
      this.decoder = decoderBuilder.build();
   }

   public JwtHmacAndAesGcmEncryptionImpl(JwtKeys keys) {
      this(keys, new RealTimeProvider(), Optional.empty());
   }

   public JwtHmacAndAesGcmEncryptionImpl(JwtKeys keys, TimeProvider timeProvider, Duration jwtLifetime) {
      this(keys, timeProvider, Optional.of(jwtLifetime));
   }

   @Override
   public JweToken hmacAndEncrypt(Plaintext plaintext) throws TicketEncodingException {
      Ticket ticket = Ticket.builder().keyMultiValue("value", plaintext.getValue()).build();
      Optional<Instant> expiry = this.jwtLifetime.map(lifetime -> this.timeProvider.getCurrentInstant().plus(lifetime));
      expiry.ifPresent(ticket::setExpiryDate);
      return new JweToken(this.encoder.encode(ticket, StandardCharsets.UTF_8.name()));
   }

   @Override
   public Plaintext decryptAndCheckHmac(JweToken jweToken) throws TicketEncodingException {
      Ticket ticket = this.decoder.decode(jweToken.getValue(), StandardCharsets.UTF_8.name());
      return new Plaintext(
         ticket.getFirstValueForName("value").orElseThrow(() -> new TicketEncodingException("The decoded ticket does not contain a key with name \"value\""))
      );
   }
}
