package com.airlock.iam.oauth2.domain.service.psd2;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.core.misc.util.crypto.CertUtils;
import com.airlock.iam.login.api.domain.model.psd2.Psd2PaymentServiceProviderRole;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1UTF8String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x509.qualified.QCStatement;

public final class Psd2CertificateRoleExtractor {
   private static final String IETF_PSD2_OID = "0.4.0.19495.2";

   private Psd2CertificateRoleExtractor() {
   }

   public static Set<Psd2PaymentServiceProviderRole> extract(X509Certificate certificate) throws Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException {
      Psd2CertificateRoleExtractor.Psd2QcStatements qcStatements = findQcStatements(certificate);
      Psd2CertificateRoleExtractor.Psd2QcStatement qcStatement = findQcStatement(qcStatements, "0.4.0.19495.2");
      Set<String> roleNames = qcStatement.psd2QcType()
         .rolesOfPSP()
         .stream()
         .map(Psd2CertificateRoleExtractor.Psd2RoleOfPSP::roleOfPspName)
         .collect(Collectors.toSet());
      return toRoles(roleNames);
   }

   private static Set<Psd2PaymentServiceProviderRole> toRoles(Set<String> roleNames) throws Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException {
      Set<Psd2PaymentServiceProviderRole> result = new HashSet<>();

      for (String roleName : roleNames) {
         Psd2PaymentServiceProviderRole role = Psd2PaymentServiceProviderRole.parse(roleName)
            .orElseThrow(() -> new Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException("Unknown PSP role: " + roleName));
         result.add(role);
      }

      if (result.isEmpty()) {
         throw new Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException("No PSP role present.");
      } else {
         return EnumSet.copyOf(result);
      }
   }

   private static Psd2CertificateRoleExtractor.Psd2QcStatements findQcStatements(X509Certificate certificate) throws Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException {
      byte[] extensionValue = certificate.getExtensionValue(CertUtils.QCSTATEMENTS);
      if (extensionValue == null) {
         throw new Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException("Certificate does not have a QCStatements extension");
      }

      DEROctetString octetString = extractDerOctetString(extensionValue);
      ASN1Sequence sequence = extractAsn1Sequence(octetString.getOctets());
      return new Psd2CertificateRoleExtractor.Psd2QcStatements(sequence);
   }

   private static DEROctetString extractDerOctetString(byte[] bytes) throws Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException {
      try {
         ASN1InputStream inputStream = new ASN1InputStream(bytes);

         DEROctetString var2;
         try {
            var2 = (DEROctetString)inputStream.readObject();
         } catch (Throwable var5) {
            try {
               inputStream.close();
            } catch (Throwable var4) {
               var5.addSuppressed(var4);
            }

            throw var5;
         }

         inputStream.close();
         return var2;
      } catch (IOException e) {
         throw new Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException("Error reading DEROctetString", e);
      }
   }

   private static ASN1Sequence extractAsn1Sequence(byte[] bytes) throws Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException {
      try {
         ASN1InputStream inputStream = new ASN1InputStream(bytes);

         ASN1Sequence var2;
         try {
            var2 = (ASN1Sequence)inputStream.readObject();
         } catch (Throwable var5) {
            try {
               inputStream.close();
            } catch (Throwable var4) {
               var5.addSuppressed(var4);
            }

            throw var5;
         }

         inputStream.close();
         return var2;
      } catch (IOException e) {
         throw new Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException("Error reading ASN1Sequence", e);
      }
   }

   private static Psd2CertificateRoleExtractor.Psd2QcStatement findQcStatement(Psd2CertificateRoleExtractor.Psd2QcStatements qcStatements, String statementId) throws Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException {
      return qcStatements.toList()
         .stream()
         .filter(qcStatement -> statementId.equals(qcStatement.getId()))
         .findFirst()
         .orElseThrow(
            () -> new Psd2CertificateRoleExtractor.Psd2CertificateRoleExtractorException("Cannot find a QCStatement with ID " + LogUtil.quote(statementId))
         );
   }

   public static final class Psd2CertificateRoleExtractorException extends Exception {
      public Psd2CertificateRoleExtractorException(String message, Throwable cause) {
         super(message, cause);
      }

      public Psd2CertificateRoleExtractorException(String message) {
         super(message);
      }
   }

   private static final class Psd2QcStatement {
      private static final int PSD2_QC_TYPE_INDEX = 1;
      private final QCStatement qcStatement;

      private Psd2QcStatement(ASN1Encodable encodable) {
         this.qcStatement = QCStatement.getInstance(encodable);
      }

      private String getId() {
         return this.qcStatement.getStatementId().getId();
      }

      private Psd2CertificateRoleExtractor.Psd2QcType psd2QcType() {
         ASN1Sequence sequence = ASN1Sequence.getInstance(this.qcStatement.toASN1Primitive());
         ASN1Encodable qcType = sequence.getObjectAt(1);
         return new Psd2CertificateRoleExtractor.Psd2QcType(qcType);
      }
   }

   private static final class Psd2QcStatements {
      private final ASN1Sequence sequence;

      private Psd2QcStatements(ASN1Sequence sequence) {
         this.sequence = sequence;
      }

      private List<Psd2CertificateRoleExtractor.Psd2QcStatement> toList() {
         return StreamSupport.<ASN1Encodable>stream(this.sequence.spliterator(), false)
            .map(Psd2CertificateRoleExtractor.Psd2QcStatement::new)
            .collect(Collectors.toList());
      }
   }

   private static final class Psd2QcType {
      private static final int ROLES_OF_PSP_INDEX = 0;
      private final ASN1Sequence sequence;

      private Psd2QcType(ASN1Encodable encodable) {
         this.sequence = ASN1Sequence.getInstance(encodable);
      }

      private List<Psd2CertificateRoleExtractor.Psd2RoleOfPSP> rolesOfPSP() {
         ASN1Sequence roles = ASN1Sequence.getInstance(this.sequence.getObjectAt(0));
         return StreamSupport.<ASN1Encodable>stream(roles.spliterator(), false)
            .map(Psd2CertificateRoleExtractor.Psd2RoleOfPSP::new)
            .collect(Collectors.toList());
      }
   }

   private static final class Psd2RoleOfPSP {
      private static final int ROLE_OF_PSP_NAME_INDEX = 1;
      private final ASN1Sequence sequence;

      private Psd2RoleOfPSP(ASN1Encodable encodable) {
         this.sequence = ASN1Sequence.getInstance(encodable);
      }

      private String roleOfPspName() {
         ASN1Encodable role = this.sequence.getObjectAt(1);
         return ASN1UTF8String.getInstance(role).getString();
      }
   }
}
