package com.airlock.iam.oauth2.domain.model.remoteconsent;

import com.airlock.iam.base.misc.time.RealTimeProvider;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.common.domain.service.jwt.JwtTicketEncoderImpl;
import com.airlock.iam.common.domain.service.jwt.encryption.JwtTicketEncrypter;
import com.airlock.iam.common.domain.service.jwt.signature.JwtSigner;
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.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.api.domain.model.OAuth2Scope;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;

public class RemoteConsentEncoder {
   private static final ELogger LOG = ELoggerFactory.getLogger(RemoteConsentEncoder.class);
   private static final String SCOPE_KEY = "scope";
   private static final String CONSENT_NONCE_KEY = "consent_nonce";
   private static final String CALLBACK_URI_KEY = "callback_uri";
   private static final String CLIENT_ID_KEY = "client_id";
   private final JwtSigner signer;
   private final JwtTicketEncrypter encrypter;
   private final Consumer<String> auditLogger;
   private final TimeProvider timeProvider;

   public RemoteConsentEncoder(JwtSigner signer, JwtTicketEncrypter encrypter, Consumer<String> auditLogger) {
      this.signer = signer;
      this.encrypter = encrypter;
      this.auditLogger = auditLogger;
      this.timeProvider = new RealTimeProvider();
   }

   public Optional<RemoteConsentEncoder.JwtEncoderResult> encode(RemoteConsentRequestToken token, RemoteConsentRequestNonce requestNonce) {
      TicketEncoder encoder = JwtTicketEncoderImpl.builder("username", this.signer, this.timeProvider)
         .claimsStoredAsArray(List.of("scope"))
         .validNotBeforeSkew(5)
         .encrypter(Optional.of(this.encrypter))
         .build();
      Ticket ticket = Ticket.builder()
         .keyMultiValue("username", token.userId())
         .keyMultiValue("scope", OAuth2Scope.convertToScopeNamesList(token.scope()))
         .keyMultiValue("consent_nonce", requestNonce.nonce())
         .keyMultiValue("callback_uri", token.callbackUri().toASCIIString())
         .keyMultiValue("client_id", token.clientId().id())
         .expiryDate(this.generateExpiryDate(token.validity()))
         .build();

      try {
         String jwt = encoder.encode(ticket, StandardCharsets.UTF_8.name());
         this.auditLogger.accept("Created Remote Consent JWT for ticket " + ticket + ". Resulting JWT: " + jwt);
         return Optional.of(new RemoteConsentEncoder.JwtEncoderResult(jwt));
      } catch (TicketEncodingException e) {
         LOG.error(EDefaultLogType.GENERIC_LOG_TYPE, "Error encoding consent token: " + e.getMessage(), e);
         return Optional.empty();
      }
   }

   private long generateExpiryDate(Duration validity) {
      return this.timeProvider.getCurrentInstant().plus(validity).toEpochMilli();
   }

   @SuppressFBWarnings(justification = "generated code")
   RemoteConsentEncoder(JwtSigner signer, JwtTicketEncrypter encrypter, Consumer<String> auditLogger, TimeProvider timeProvider) {
      this.signer = signer;
      this.encrypter = encrypter;
      this.auditLogger = auditLogger;
      this.timeProvider = timeProvider;
   }

   public static final class JwtEncoderResult {
      private final String jwt;

      @SuppressFBWarnings(justification = "generated code")
      public JwtEncoderResult(String jwt) {
         this.jwt = jwt;
      }

      @SuppressFBWarnings(justification = "generated code")
      public String jwt() {
         return this.jwt;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof RemoteConsentEncoder.JwtEncoderResult other)) {
            return false;
         } else {
            Object this$jwt = this.jwt;
            Object other$jwt = other.jwt;
            return this$jwt == null ? other$jwt == null : this$jwt.equals(other$jwt);
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         Object $jwt = this.jwt;
         return result * 59 + ($jwt == null ? 43 : $jwt.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "RemoteConsentEncoder.JwtEncoderResult(jwt=" + this.jwt() + ")";
      }
   }
}
