package com.airlock.iam.factor.infrastructure.service.fido;

import com.airlock.iam.factor.domain.model.fido.FidoCredentialDisplayName;
import com.airlock.iam.factor.domain.model.fido.FidoCredentialName;
import com.airlock.iam.factor.domain.model.fido.FidoRelyingPartyName;
import com.airlock.iam.factor.domain.model.fido.FidoTimeout;
import com.airlock.iam.factor.domain.model.fido.SupportedCoseAlgorithmIdentifier;
import com.airlock.iam.factor.domain.model.fido.registration.FidoAttestationType;
import com.airlock.iam.factor.domain.model.fido.registration.FidoAuthenticatorType;
import com.airlock.iam.factor.domain.model.fido.registration.FidoUserVerificationPreference;
import com.airlock.iam.fido.api.domain.model.FidoBinaryValue;
import com.airlock.iam.fido.api.domain.model.FidoRelyingPartyId;
import com.yubico.webauthn.data.AttestationConveyancePreference;
import com.yubico.webauthn.data.AuthenticatorAttachment;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.COSEAlgorithmIdentifier;
import com.yubico.webauthn.data.PublicKeyCredentialParameters;
import com.yubico.webauthn.data.UserVerificationRequirement;
import com.yubico.webauthn.data.exception.Base64UrlException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

final class YubicoDecoder {
   private static final Function<String, ByteArray> DECODER_BASE64_URL = s -> {
      try {
         return ByteArray.fromBase64Url(s);
      } catch (Base64UrlException e) {
         throw new IllegalArgumentException("Invalid Base64Url formatted string", e);
      }
   };

   private YubicoDecoder() {
      throw new AssertionError();
   }

   static ByteArray decode(FidoBinaryValue value) {
      return new ByteArray(value.bytes());
   }

   static ByteArray decodeBase64Url(String base64UrlData) {
      return DECODER_BASE64_URL.apply(base64UrlData);
   }

   static ByteArray decodeClientDataJson(String clientDataJson) {
      return new ByteArray(clientDataJson.getBytes(StandardCharsets.UTF_8));
   }

   static AttestationConveyancePreference attestationConveyancePreference(FidoAttestationType attestationType) {
      switch (attestationType) {
         case INDIRECT:
            return AttestationConveyancePreference.INDIRECT;
         case DIRECT:
            return AttestationConveyancePreference.DIRECT;
         case NONE:
            return AttestationConveyancePreference.NONE;
         default:
            throw new IllegalStateException(String.format("Unknown FIDO attestation conveyance type '%s'", attestationType));
      }
   }

   static UserVerificationRequirement userVerificationRequirement(FidoUserVerificationPreference preference) {
      switch (preference) {
         case DISCOURAGED:
            return UserVerificationRequirement.DISCOURAGED;
         case PREFERRED:
            return UserVerificationRequirement.PREFERRED;
         case REQUIRED:
            return UserVerificationRequirement.REQUIRED;
         default:
            throw new IllegalStateException(String.format("Unknown FIDO user verification preference '%s'", preference));
      }
   }

   static Optional<AuthenticatorAttachment> authenticatorAttachment(FidoAuthenticatorType type) {
      switch (type) {
         case ROAMING_ONLY:
            return Optional.of(AuthenticatorAttachment.CROSS_PLATFORM);
         case BOUND_ONLY:
            return Optional.of(AuthenticatorAttachment.PLATFORM);
         case ALL:
            return Optional.empty();
         default:
            throw new IllegalStateException(String.format("Unknown FIDO authenticator type '%s'", type));
      }
   }

   static List<PublicKeyCredentialParameters> publicKeyCredentialParameters(List<SupportedCoseAlgorithmIdentifier> preferredAlgorithms) {
      return preferredAlgorithms.stream()
         .map(
            alg -> PublicKeyCredentialParameters.builder()
               .alg(
                  (COSEAlgorithmIdentifier)COSEAlgorithmIdentifier.fromId(alg.getId())
                     .orElseThrow(() -> new IllegalStateException(String.format("Algorithm '%s' is not supported by our FIDO library", alg)))
               )
               .build()
         )
         .collect(Collectors.toUnmodifiableList());
   }

   static long timeoutInMillis(FidoTimeout timeout) {
      return timeout.toMillis();
   }

   static String credentialName(FidoCredentialName credentialName) {
      return credentialName.getName();
   }

   static String credentialDisplayName(FidoCredentialDisplayName displayName) {
      return displayName.getName();
   }

   static String relyingPartyId(FidoRelyingPartyId relyingPartyId) {
      return relyingPartyId.getId();
   }

   static String relyingPartyName(FidoRelyingPartyName relyingPartyName) {
      return relyingPartyName.getName();
   }
}
