package com.airlock.iam.saml2.infrastructure.session;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.api.domain.model.user.UsernameToPropagate;
import com.airlock.iam.flow.shared.domain.model.session.SuccessfulAuthenticationTimestamp;
import com.airlock.iam.saml2.domain.model.Saml2RelayState;
import com.airlock.iam.saml2.domain.model.assertion.attribute.Saml2AttributeProvider;
import com.airlock.iam.saml2.domain.model.idp.Saml2AuthenticationContextClassRef;
import com.airlock.iam.saml2.domain.model.idp.Saml2IdpEntityId;
import com.airlock.iam.saml2.domain.model.idp.Saml2IdpSsoContinuationId;
import com.airlock.iam.saml2.domain.model.idp.Saml2SpInitiatedRequestAttribute;
import com.airlock.iam.saml2.domain.model.session.Saml2PropagationAttributes;
import com.airlock.iam.saml2.domain.model.session.Saml2PropagationAttributesFactory;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpEntityId;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import lombok.NonNull;

public final class Saml2PropagationAttributesFactoryImpl implements Saml2PropagationAttributesFactory {
   private static final Duration PROPAGATION_ATTRIBUTES_LIFETIME = Duration.ofMinutes(1L);
   private final Supplier<Saml2IdpSsoContinuationId> identityPropagationIdGenerator;
   private final Provider<Saml2IdpEntityId> idpEntityId;
   private final Provider<Optional<SuccessfulAuthenticationTimestamp>> authenticationTimestamp;
   private final Provider<Optional<Saml2AuthenticationContextClassRef>> authenticationContext;
   private final Provider<List<Saml2AttributeProvider>> attributeProviders;
   private final TimeProvider timeProvider;

   @Inject
   Saml2PropagationAttributesFactoryImpl(
      @NonNull Supplier<Saml2IdpSsoContinuationId> identityPropagationIdGenerator,
      @NonNull Provider<Saml2IdpEntityId> idpEntityId,
      @NonNull Provider<Optional<SuccessfulAuthenticationTimestamp>> authenticationTimestamp,
      @NonNull Provider<Optional<Saml2AuthenticationContextClassRef>> authenticationContext,
      @NonNull Provider<List<Saml2AttributeProvider>> attributeProviders,
      @NonNull TimeProvider timeProvider
   ) {
      if (identityPropagationIdGenerator == null) {
         throw new NullPointerException("identityPropagationIdGenerator is marked non-null but is null");
      }

      if (idpEntityId == null) {
         throw new NullPointerException("idpEntityId is marked non-null but is null");
      }

      if (authenticationTimestamp == null) {
         throw new NullPointerException("authenticationTimestamp is marked non-null but is null");
      }

      if (authenticationContext == null) {
         throw new NullPointerException("authenticationContext is marked non-null but is null");
      }

      if (attributeProviders == null) {
         throw new NullPointerException("attributeProviders is marked non-null but is null");
      }

      if (timeProvider == null) {
         throw new NullPointerException("timeProvider is marked non-null but is null");
      }

      this.identityPropagationIdGenerator = identityPropagationIdGenerator;
      this.idpEntityId = idpEntityId;
      this.authenticationTimestamp = authenticationTimestamp;
      this.authenticationContext = authenticationContext;
      this.attributeProviders = attributeProviders;
      this.timeProvider = timeProvider;
   }

   @Override
   public Saml2PropagationAttributes createIdpInitiatedAttributes(
      UserIdentity userIdentity, UsernameToPropagate usernameToPropagate, Saml2SpEntityId spEntityId, Optional<Saml2RelayState> relayState
   ) {
      Saml2PropagationAttributes.Saml2PropagationAttributesBuilder attributesBuilder = this.attributesBuilder(usernameToPropagate, spEntityId);
      relayState.ifPresent(attributesBuilder::relayState);
      return attributesBuilder.build();
   }

   @Override
   public Saml2PropagationAttributes createSpInitiatedAttributes(
      UserIdentity userIdentity, UsernameToPropagate usernameToPropagate, Saml2SpInitiatedRequestAttribute authnRequestInfo
   ) {
      Saml2PropagationAttributes.Saml2PropagationAttributesBuilder attributesBuilder = this.attributesBuilder(
         usernameToPropagate, authnRequestInfo.getSpEntityId()
      );
      attributesBuilder.spInitiatedRequestId(authnRequestInfo.getRequestId());
      return attributesBuilder.build();
   }

   private Saml2PropagationAttributes.Saml2PropagationAttributesBuilder attributesBuilder(UsernameToPropagate usernameToPropagate, Saml2SpEntityId spEntityId) {
      Saml2PropagationAttributes.Saml2PropagationAttributesBuilder attributesBuilder = Saml2PropagationAttributes.builder()
         .ssoContinuationId(this.identityPropagationIdGenerator.get())
         .spEntityId(spEntityId)
         .idpEntityId((Saml2IdpEntityId)this.idpEntityId.get())
         .expiryInstant(this.timeProvider.getCurrentInstant().plus(PROPAGATION_ATTRIBUTES_LIFETIME))
         .usernameToPropagate(usernameToPropagate)
         .authenticationInstant(
            ((SuccessfulAuthenticationTimestamp)((Optional)this.authenticationTimestamp.get())
                  .orElseThrow(() -> new FailureException("No SuccessfulAuthenticationTimestamp available")))
               .getAuthenticationTime()
         )
         .assertionAttributes(((List)this.attributeProviders.get()).stream().map(Saml2AttributeProvider::getAttribute).flatMap(Optional::stream).toList());
      ((Optional)this.authenticationContext.get()).ifPresent(attributesBuilder::authenticationContext);
      return attributesBuilder;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static Saml2PropagationAttributesFactoryImpl.Saml2PropagationAttributesFactoryImplBuilder builder() {
      return new Saml2PropagationAttributesFactoryImpl.Saml2PropagationAttributesFactoryImplBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class Saml2PropagationAttributesFactoryImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Supplier<Saml2IdpSsoContinuationId> identityPropagationIdGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Saml2IdpEntityId> idpEntityId;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Optional<SuccessfulAuthenticationTimestamp>> authenticationTimestamp;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Optional<Saml2AuthenticationContextClassRef>> authenticationContext;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<List<Saml2AttributeProvider>> attributeProviders;
      @SuppressFBWarnings(justification = "generated code")
      private TimeProvider timeProvider;

      @SuppressFBWarnings(justification = "generated code")
      Saml2PropagationAttributesFactoryImplBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2PropagationAttributesFactoryImpl.Saml2PropagationAttributesFactoryImplBuilder identityPropagationIdGenerator(
         @NonNull Supplier<Saml2IdpSsoContinuationId> identityPropagationIdGenerator
      ) {
         if (identityPropagationIdGenerator == null) {
            throw new NullPointerException("identityPropagationIdGenerator is marked non-null but is null");
         }

         this.identityPropagationIdGenerator = identityPropagationIdGenerator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2PropagationAttributesFactoryImpl.Saml2PropagationAttributesFactoryImplBuilder idpEntityId(@NonNull Provider<Saml2IdpEntityId> idpEntityId) {
         if (idpEntityId == null) {
            throw new NullPointerException("idpEntityId is marked non-null but is null");
         }

         this.idpEntityId = idpEntityId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2PropagationAttributesFactoryImpl.Saml2PropagationAttributesFactoryImplBuilder authenticationTimestamp(
         @NonNull Provider<Optional<SuccessfulAuthenticationTimestamp>> authenticationTimestamp
      ) {
         if (authenticationTimestamp == null) {
            throw new NullPointerException("authenticationTimestamp is marked non-null but is null");
         }

         this.authenticationTimestamp = authenticationTimestamp;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2PropagationAttributesFactoryImpl.Saml2PropagationAttributesFactoryImplBuilder authenticationContext(
         @NonNull Provider<Optional<Saml2AuthenticationContextClassRef>> authenticationContext
      ) {
         if (authenticationContext == null) {
            throw new NullPointerException("authenticationContext is marked non-null but is null");
         }

         this.authenticationContext = authenticationContext;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2PropagationAttributesFactoryImpl.Saml2PropagationAttributesFactoryImplBuilder attributeProviders(
         @NonNull Provider<List<Saml2AttributeProvider>> attributeProviders
      ) {
         if (attributeProviders == null) {
            throw new NullPointerException("attributeProviders is marked non-null but is null");
         }

         this.attributeProviders = attributeProviders;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2PropagationAttributesFactoryImpl.Saml2PropagationAttributesFactoryImplBuilder timeProvider(@NonNull TimeProvider timeProvider) {
         if (timeProvider == null) {
            throw new NullPointerException("timeProvider is marked non-null but is null");
         }

         this.timeProvider = timeProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2PropagationAttributesFactoryImpl build() {
         return new Saml2PropagationAttributesFactoryImpl(
            this.identityPropagationIdGenerator,
            this.idpEntityId,
            this.authenticationTimestamp,
            this.authenticationContext,
            this.attributeProviders,
            this.timeProvider
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Saml2PropagationAttributesFactoryImpl.Saml2PropagationAttributesFactoryImplBuilder(identityPropagationIdGenerator="
            + this.identityPropagationIdGenerator
            + ", idpEntityId="
            + this.idpEntityId
            + ", authenticationTimestamp="
            + this.authenticationTimestamp
            + ", authenticationContext="
            + this.authenticationContext
            + ", attributeProviders="
            + this.attributeProviders
            + ", timeProvider="
            + this.timeProvider
            + ")";
      }
   }
}
