package com.airlock.iam.ssi.domain.model.step;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.session.FlowAttributeRepository;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.api.domain.model.step.StepSession;
import com.airlock.iam.flow.shared.domain.service.PollingService;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.ssi.application.configuration.step.SsiVerificationStepConfig;
import com.airlock.iam.ssi.domain.model.SsiPollingStatus;
import com.airlock.iam.ssi.domain.model.SsiVerificationSession;
import com.airlock.iam.ssi.domain.model.SsiVerifiedData;
import com.airlock.iam.ssi.domain.model.attribute.SsiVerificationData;
import com.airlock.iam.ssi.domain.service.SsiService;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.net.URI;
import java.util.Map;
import java.util.Optional;

@LogicForConfig(SsiVerificationStepConfig.class)
public final class SsiVerificationStepImpl implements SsiVerificationStep {
   private static final ELogger LOG = ELoggerFactory.getLogger(SsiVerificationStepImpl.class);
   private final Provider<SsiService> service;
   private final Provider<SsiVerificationStepSession> session;
   private final Provider<SsiVerificationStepSettings> settings;
   private final Provider<FlowAttributeRepository> attributes;
   private final PollingService<SsiVerificationStepSession, StepResult> verificationPolling;
   private final Provider<PrincipalInfo> user;
   private final HelpdeskLogger helpdeskLogger;

   @Inject
   public SsiVerificationStepImpl(
      Provider<SsiService> service,
      Provider<SsiVerificationStepSession> session,
      Provider<SsiVerificationStepSettings> settings,
      Provider<FlowAttributeRepository> attributes,
      Provider<PrincipalInfo> user,
      HelpdeskLogger helpdeskLogger
   ) {
      this.service = service;
      this.session = session;
      this.settings = settings;
      this.attributes = attributes;
      this.user = user;
      this.helpdeskLogger = helpdeskLogger;
      this.verificationPolling = new PollingService<>(
         SsiVerificationStepSession::getVerificationCache, this::pollVerificationInternal, s -> this.verificationRequiredResult()
      );
   }

   @Override
   public Optional<StepSession> createSession() {
      return Optional.of(SsiVerificationStepSession.emptySession());
   }

   @Override
   public StepResult pollProofVerification(boolean queryOnly) {
      return this.verificationPolling.poll((SsiVerificationStepSession)this.session.get(), queryOnly);
   }

   private StepResult pollVerificationInternal(SsiVerificationStepSession stepSession) {
      if (stepSession.getVerificationSession().isEmpty()) {
         return this.stepResultFactory().failDueToUnexpectedCall("Tried to poll proof verification, but proof has not been requested.").build();
      }

      Result<SsiVerifiedData, SsiPollingStatus> result = ((SsiService)this.service.get())
         .pollProofStatus(stepSession.getVerificationSession().orElseThrow(), ((SsiVerificationStepSettings)this.settings.get()).parameters());
      if (result.isPresent()) {
         LOG.info("Successfully verified presentation.");
         Map<String, String> receivedAttributes = result.get().providerKeysToValues();
         ((FlowAttributeRepository)this.attributes.get()).putOrMergeAttribute(new SsiVerificationData(receivedAttributes));
         String info = "Received verified attributes %s";
         this.currentUser().ifPresent(currentUser -> this.helpdeskLogger.log(currentUser, info, new Object[]{receivedAttributes.keySet()}));
         return this.stepResultFactory().success().build();
      }

      return switch ((SsiPollingStatus)result.getFailure()) {
         case PENDING -> this.verificationRequiredResult();
         case FAILED -> {
            LOG.info("Failed to verify presentation.");
            this.currentUser().ifPresent(currentUser -> this.helpdeskLogger.log(currentUser, "Failed to verify presentation."));
            yield this.stepResultFactory().fail(SsiVerificationStep.SsiVerificationErrors.SSI_VERIFICATION_FAILED).build();
         }
         case SUCCESS -> throw new IllegalStateException("not possible");
      };
   }

   private StepResult verificationRequiredResult() {
      return SsiUtils.addInvitationAttributes(
         this.stepResultFactory().stay(SsiVerificationStep.SsiVerificationActions.SSI_PROOF_VERIFICATION_REQUIRED),
         ((SsiVerificationStepSession)this.session.get()).getInvitationLink().orElseThrow(),
         ((SsiVerificationStepSession)this.session.get()).getInvitationQrCode().orElseThrow()
      );
   }

   @Override
   public String getDescription() {
      return "SSI Verification Step";
   }

   @Override
   public StepResult initialize() {
      Optional<SsiVerificationSession> proofRequest = ((SsiService)this.service.get())
         .initiateProofRequest(((SsiVerificationStepSettings)this.settings.get()).parameters());
      if (proofRequest.isEmpty()) {
         LOG.info("The verification proof request could not be initiated.");
         return this.stepResultFactory().fail(SsiVerificationStep.SsiVerificationErrors.SSI_VERIFICATION_FAILED).build();
      } else {
         URI invitationLink = URI.create(proofRequest.get().urlForQrCode());
         String invitationQrString = SsiUtils.generateQrAsString(invitationLink.toASCIIString());
         ((SsiVerificationStepSession)this.session.get()).proofRequested(proofRequest.get(), invitationLink, invitationQrString);
         return this.verificationRequiredResult();
      }
   }

   private Optional<UserId> currentUser() {
      return ((PrincipalInfo)this.user.get()).getKnownIdentity().map(UserIdentity::id);
   }
}
