package com.airlock.iam.admin.application.service.analytics;

import com.airlock.iam.admin.application.service.license.LicenseService;
import com.airlock.iam.admin.domain.model.analytics.AnalyticsAuthKey;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.base.misc.util.Base64Util;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWEHeader.Builder;
import com.nimbusds.jose.crypto.DirectEncrypter;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWTClaimsSet;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;

public class AnalyticsAccessTokenService {
   private static final JWEAlgorithm DEFAULT_ALGORITHM = JWEAlgorithm.DIR;
   private static final EncryptionMethod ENCRYPTION_METHOD = EncryptionMethod.A256GCM;
   private static final Duration ACCESS_TOKEN_LIFETIME = Duration.ofMinutes(5L);
   private static final String ISSUED_AT_CLAIM = "iat";
   private static final String EXPIRY_CLAIM = "exp";
   private static final String JWT_ID_CLAIM = "jti";
   private static final String SUBJECT_CLAIM = "sub";
   private static final String HASH_SALT = "XESEGui0telxFQMZOI0vzaoZIykZKf5ujjDKmxR+pM8=";
   public static final String FALLBACK_LICENSE_TEXT = "unlicensed";
   private final Provider<TimeProvider> time;
   private final Provider<LicenseService> licenseService;

   @Inject
   public AnalyticsAccessTokenService(Provider<TimeProvider> time, Provider<LicenseService> licenseService) {
      this.time = time;
      this.licenseService = licenseService;
   }

   public String generateAccessToken(AnalyticsAuthKey authKey) {
      JWEHeader header = new Builder(DEFAULT_ALGORITHM, ENCRYPTION_METHOD).type(JOSEObjectType.JWT).build();
      EncryptedJWT jwt = new EncryptedJWT(header, this.claimSet());

      try {
         DirectEncrypter encrypter = new DirectEncrypter(authKey.asByteArray());
         jwt.encrypt(encrypter);
      } catch (JOSEException e) {
         throw new FailureException(e);
      }

      return jwt.serialize();
   }

   private JWTClaimsSet claimSet() {
      Instant now = ((TimeProvider)this.time.get()).getCurrentInstant();

      try {
         return JWTClaimsSet.parse(
            Map.of(
               "iat",
               now.getEpochSecond(),
               "exp",
               now.plus(ACCESS_TOKEN_LIFETIME).getEpochSecond(),
               "jti",
               UUID.randomUUID().toString(),
               "sub",
               this.licenseHash()
            )
         );
      } catch (ParseException e) {
         throw new FailureException(e);
      }
   }

   private String licenseHash() {
      return ((LicenseService)this.licenseService.get()).getActiveLicenseText().map(AnalyticsAccessTokenService::hash).orElse("unlicensed");
   }

   private static String hash(String licenseText) {
      try {
         MessageDigest digest = MessageDigest.getInstance("SHA-256");
         String saltedLicenseText = "XESEGui0telxFQMZOI0vzaoZIykZKf5ujjDKmxR+pM8=" + licenseText;
         byte[] hash = digest.digest(saltedLicenseText.getBytes(StandardCharsets.UTF_8));
         return Base64Util.encodeToString(hash);
      } catch (NoSuchAlgorithmException e) {
         throw new FailureException(e);
      }
   }
}
