package com.airlock.iam.admin.application.service.token.airlock2fa;

import com.airlock.iam.admin.api.domain.model.administrator.CurrentAdministrator;
import com.airlock.iam.admin.domain.model.token.airlock2fa.Airlock2FAShipmentLetter;
import com.airlock.iam.admin.domain.model.token.airlock2fa.Airlock2FAUserAccount;
import com.airlock.iam.admin.domain.service.token.airlock2fa.Airlock2FAShipmentLetterPrintingService;
import com.airlock.iam.admin.domain.service.token.airlock2fa.Airlock2FAUserAccountRepository;
import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FADeviceId;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAUser;
import com.airlock.iam.factor.domain.model.airlock2fa.activation.Airlock2FAActivationLetter;
import com.airlock.iam.factor.domain.model.airlock2fa.activation.Airlock2FAActivationLetterId;
import com.airlock.iam.factor.domain.model.airlock2fa.hardware.Airlock2FAAdminHardwareDevice;
import com.airlock.iam.factor.domain.model.airlock2fa.order.Airlock2FAActivationLetterOrder;
import com.airlock.iam.factor.domain.model.airlock2fa.order.Airlock2FAActivationLetterOrderCreator;
import com.airlock.iam.factor.domain.model.airlock2fa.order.Airlock2FAActivationLetterOrderId;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAActivationLetterPrintingService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAActivationLetterService;
import com.airlock.iam.factor.domain.service.airlock2fa.activation.Airlock2FAActivationLetterOrderService;
import com.airlock.iam.factor.domain.service.airlock2fa.activation.Airlock2FAFindActivationLetterOrderService;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.log.api.signing.SigningLogger;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import lombok.NonNull;

public final class Airlock2FAAdminService {
   private static final ELogger LOG = ELoggerFactory.getLogger(Airlock2FAAdminService.class);
   private final Airlock2FAUserAccountRepository accountRepository;
   private final Optional<Airlock2FAActivationLetterService> activationLetterService;
   private final Optional<Airlock2FAFindActivationLetterOrderService> findActivationOrderService;
   private final Optional<Airlock2FAActivationLetterOrderService> activationOrderService;
   private final Optional<Airlock2FAActivationLetterPrintingService> activationLetterPrintingService;
   private final Optional<Airlock2FAShipmentLetterPrintingService> shipmentLetterPrintingService;
   private final CurrentAdministrator administrator;
   private final SigningLogger signingLogger;
   private final HelpdeskLogger helpdeskLogger;

   @Inject
   public Airlock2FAAdminService(
      @NonNull Airlock2FAUserAccountRepository accountRepository,
      @NonNull Optional<Airlock2FAActivationLetterService> activationLetterService,
      @NonNull Optional<Airlock2FAFindActivationLetterOrderService> findActivationOrderService,
      @NonNull Optional<Airlock2FAActivationLetterOrderService> activationOrderService,
      @NonNull Optional<Airlock2FAActivationLetterPrintingService> activationLetterPrintingService,
      @NonNull Optional<Airlock2FAShipmentLetterPrintingService> shipmentLetterPrintingService,
      @NonNull CurrentAdministrator administrator,
      @NonNull SigningLogger signingLogger,
      @NonNull HelpdeskLogger helpdeskLogger
   ) {
      if (accountRepository == null) {
         throw new NullPointerException("accountRepository is marked non-null but is null");
      }

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

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

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

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

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

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

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

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

      this.accountRepository = accountRepository;
      this.activationLetterService = activationLetterService;
      this.findActivationOrderService = findActivationOrderService;
      this.activationOrderService = activationOrderService;
      this.activationLetterPrintingService = activationLetterPrintingService;
      this.shipmentLetterPrintingService = shipmentLetterPrintingService;
      this.administrator = administrator;
      this.signingLogger = signingLogger;
      this.helpdeskLogger = helpdeskLogger;
   }

   public Optional<Airlock2FAUserAccount> findAccountBy(UserId userId) {
      return this.accountRepository.findBy(userId);
   }

   public Airlock2FAUserAccount createAccount(UserId userId) {
      if (this.findAccountBy(userId).isPresent()) {
         throw new ViolationException(String.format("User %s already has an Airlock 2FA account", LogUtil.quote(userId)));
      }

      Airlock2FAUserAccount account = this.accountRepository.createAccount(userId);
      this.signingLogger.log(String.format("Added new Airlock 2FA account to user %s", LogUtil.quote(userId)));
      this.helpdeskLogger.log(userId, "Administrator ${authentee} created an Airlock 2FA account.");
      return account;
   }

   public void deleteAccount(UserId userId) {
      Airlock2FAUserAccount account = this.getAccountOrThrow(userId);
      this.accountRepository.delete(account);
      this.signingLogger.log(String.format("Deleted Airlock 2FA account to user %s", LogUtil.quote(userId)));
      this.helpdeskLogger.log(userId, "Administrator ${authentee} deleted Airlock 2FA account.");
   }

   public void lockAccount(UserId userId) {
      Airlock2FAUserAccount account = this.getAccountOrThrow(userId);
      account.lock();
      this.accountRepository.update(account);
      this.signingLogger.log(String.format("Locked Airlock 2FA account for user %s", LogUtil.quote(userId)));
      this.helpdeskLogger.log(userId, "Administrator ${authentee} locked Airlock 2FA account.");
   }

   public void unlockAccount(UserId userId) {
      Airlock2FAUserAccount account = this.getAccountOrThrow(userId);
      account.unlock();
      this.accountRepository.update(account);
      this.signingLogger.log(String.format("Unlocked Airlock 2FA account for user %s", LogUtil.quote(userId)));
      this.helpdeskLogger.log(userId, "Administrator ${authentee} unlocked Airlock 2FA account.");
   }

   public void unenrollDevice(UserId userId, Airlock2FADeviceId airlock2FADeviceId) {
      Airlock2FAUserAccount account = this.getAccountOrThrow(userId);
      account.removeDeviceById(airlock2FADeviceId);
      this.accountRepository.update(account);
      this.signingLogger
         .log(String.format("Removed device with ID %s from Airlock 2FA Account for user %s", LogUtil.quote(airlock2FADeviceId.getId()), LogUtil.quote(userId)));
      this.helpdeskLogger
         .log(userId, "Administrator ${authentee} unenrolled the Airlock 2FA device with ID %s.", new Object[]{LogUtil.quote(airlock2FADeviceId.getId())});
   }

   public Result<Airlock2FAActivationLetter, Airlock2FAAdminService.CreateLetterFailure> createActivationLetter(UserId userId) {
      Airlock2FAUserAccount account = this.getAccountOrThrow(userId);
      UserId creator = UserId.userId(this.administrator.getName());
      if (this.activationLetterService.isEmpty()) {
         LOG.info("Cannot create activation letter because no activation letter configuration is present");
         return Result.failure(Airlock2FAAdminService.CreateLetterFailure.LETTER_NOT_CONFIGURED);
      }

      if (account.isPermanentlyDisabled()) {
         LOG.info("Cannot create activation letter for permanently disabled account: " + userId);
         return Result.failure(Airlock2FAAdminService.CreateLetterFailure.ACCOUNT_PERMANENTLY_DISABLED);
      }

      Airlock2FAActivationLetter activationLetter = this.activationLetterService
         .get()
         .create(Airlock2FAUser.airlock2FAUser(userId, account.getAccountId()), creator);
      if (this.activationLetterPrintingService.isPresent()) {
         Path printableFile = this.activationLetterPrintingService.get().printToFile(userId, activationLetter);
         this.helpdeskLogger
            .log(userId, String.format("Administrator ${authentee} printed a new Airlock 2FA activation letter %s.", LogUtil.quote(printableFile)));
      }

      return Result.result(activationLetter);
   }

   public Optional<Airlock2FAAdminService.DeleteLetterFailure> deleteActivationLetter(UserId userId, Airlock2FAActivationLetterId letterId) {
      if (this.activationLetterService.isEmpty()) {
         LOG.info("Cannot delete activation letter because no activation letter configuration is present");
         return Optional.of(Airlock2FAAdminService.DeleteLetterFailure.LETTERS_NOT_CONFIGURED);
      }

      Optional<Airlock2FAUserAccount> account = this.accountRepository.findBy(userId);
      if (account.isEmpty()) {
         LOG.info("No Airlock 2FA account found for user " + LogUtil.quote(userId));
         return Optional.of(Airlock2FAAdminService.DeleteLetterFailure.LETTER_NOT_FOUND);
      }

      Optional<Airlock2FAActivationLetter> letterToDelete = account.get()
         .getActivationLetters()
         .stream()
         .filter(letter -> letterId.equals(letter.getId()))
         .findFirst();
      if (letterToDelete.isEmpty()) {
         LOG.info("No Airlock 2FA activation letter found with ID " + LogUtil.logSafeQuote(letterId.id()) + " for user " + LogUtil.quote(userId));
         return Optional.of(Airlock2FAAdminService.DeleteLetterFailure.LETTER_NOT_FOUND);
      }

      boolean success = this.activationLetterService.get().delete(letterToDelete.get());
      if (!success) {
         return Optional.of(Airlock2FAAdminService.DeleteLetterFailure.FAILED_TO_DELETE);
      }

      this.signingLogger
         .log(String.format("Deleted Airlock 2FA activation letter with ID %s for user %s.", LogUtil.quote(letterId.id()), LogUtil.quote(userId)));
      this.helpdeskLogger
         .log(
            userId,
            String.format(
               "Administrator %s deleted Airlock 2FA activation letter with ID %s.", LogUtil.quote(this.administrator.getName()), LogUtil.quote(letterId.id())
            )
         );
      return Optional.empty();
   }

   public Result<Airlock2FAActivationLetterOrder, Airlock2FAAdminService.ActivationLetterOrderFailure> createActivationLetterOrder(UserId userId) {
      if (this.activationOrderService.isEmpty()) {
         LOG.info("Cannot create activation letter order because no activation letter order configuration is present");
         return Result.failure(Airlock2FAAdminService.ActivationLetterOrderFailure.NOT_CONFIGURED);
      } else {
         Airlock2FAUserAccount account = this.getAccountOrThrow(userId);
         Airlock2FAActivationLetterOrderCreator creator = Airlock2FAActivationLetterOrderCreator.orderCreator(UserId.userId(this.administrator.getName()));
         if (account.isPermanentlyDisabled()) {
            LOG.info("Cannot create activation letter order for permanently disabled account: " + userId);
            return Result.failure(Airlock2FAAdminService.ActivationLetterOrderFailure.ACCOUNT_PERMANENTLY_DISABLED);
         } else {
            Optional<Airlock2FAActivationLetterOrder> order = this.activationOrderService.get().createOrder(account.getAccountId(), creator);
            if (order.isEmpty()) {
               LOG.info(String.format("Cannot create activation letter order for: %s, since user already has a standing activation letter order", userId));
               return Result.failure(Airlock2FAAdminService.ActivationLetterOrderFailure.ORDER_ALREADY_EXISTS);
            } else {
               Airlock2FAActivationLetterOrder createdOrder = order.get();
               this.signingLogger.log(String.format("Created a new Airlock 2FA activation letter order %s.", createdOrder));
               this.helpdeskLogger
                  .log(userId, String.format("Administrator %s created a new Airlock 2FA activation letter order with ID %s.", creator, createdOrder.getId()));
               return Result.result(createdOrder);
            }
         }
      }
   }

   public List<Airlock2FAActivationLetterOrder> findOpenActivationLetterOrders(UserId userId) {
      if (this.findActivationOrderService.isEmpty()) {
         LOG.debug("Cannot find activation letter order because no activation letter order configuration is present");
         return List.of();
      } else {
         Airlock2FAUserAccount account = this.getAccountOrThrow(userId);
         return this.findActivationOrderService.get().findOpenOrders(account.getAccountId());
      }
   }

   public Result<Boolean, Airlock2FAAdminService.ActivationLetterOrderFailure> deleteOrder(UserId userId, Airlock2FAActivationLetterOrderId orderId) {
      if (this.activationOrderService.isEmpty()) {
         LOG.info("Cannot delete activation letter order because no activation letter order configuration is present");
         return Result.failure(Airlock2FAAdminService.ActivationLetterOrderFailure.NOT_CONFIGURED);
      }

      Airlock2FAUserAccount account = this.getAccountOrThrow(userId);
      boolean orderDeleted = this.activationOrderService.get().deleteOrder(account.getAccountId(), orderId);
      if (!orderDeleted) {
         return Result.failure(Airlock2FAAdminService.ActivationLetterOrderFailure.ORDER_NOT_FOUND);
      }

      this.signingLogger.log(String.format("Deleted the Airlock 2FA activation letter order with ID %s.", orderId.getId()));
      this.helpdeskLogger
         .log(
            userId,
            String.format("Administrator %s deleted the Airlock 2FA activation letter order with ID '%s'.", this.administrator.getName(), orderId.getId())
         );
      return Result.result(Boolean.TRUE);
   }

   public Result<Airlock2FAShipmentLetter, Airlock2FAAdminService.CreateLetterFailure> createShipmentLetter(UserId userId, Airlock2FADeviceId deviceId) {
      if (this.shipmentLetterPrintingService.isEmpty()) {
         LOG.info("Cannot create shipment letter because no shipment letter configuration is present");
         return Result.failure(Airlock2FAAdminService.CreateLetterFailure.LETTER_NOT_CONFIGURED);
      } else {
         Airlock2FAUserAccount account = this.getAccountOrThrow(userId);
         if (account.isPermanentlyDisabled()) {
            LOG.info("Cannot create shipment letter for permanently disabled account: " + userId);
            return Result.failure(Airlock2FAAdminService.CreateLetterFailure.ACCOUNT_PERMANENTLY_DISABLED);
         } else {
            Airlock2FAAdminHardwareDevice hardwareDevice = account.findHardwareDeviceWithId(deviceId)
               .orElseThrow(() -> new FailureException(String.format("Hardware device '%s' is not assigned to user '%s'", deviceId.getId(), userId)));
            return Result.result(toShipmentLetter(userId, hardwareDevice));
         }
      }
   }

   public void printShipmentLetter(Airlock2FAShipmentLetter shipmentLetter) {
      UserId userId = shipmentLetter.getDeviceOwner();
      Path printableFile = this.shipmentLetterPrintingService.orElseThrow().printToFile(userId, shipmentLetter);
      this.signingLogger
         .log(
            String.format(
               "Admin %s created a new Airlock 2FA shipment letter %s for user %s.",
               LogUtil.quote(UserId.userId(this.administrator.getName())),
               LogUtil.quote(printableFile),
               LogUtil.quote(userId)
            )
         );
      this.helpdeskLogger.log(userId, "Administrator ${authentee} printed a new Airlock 2FA shipment letter.");
   }

   private Airlock2FAUserAccount getAccountOrThrow(UserId userId) {
      return (Airlock2FAUserAccount)ViolationException.checkPresent(
         this.accountRepository.findBy(userId), String.format("No Airlock 2FA account found for user %s", LogUtil.quote(userId))
      );
   }

   private static Airlock2FAShipmentLetter toShipmentLetter(UserId userId, Airlock2FAAdminHardwareDevice hardwareDevice) {
      return Airlock2FAShipmentLetter.builder()
         .deviceOwner(userId)
         .deviceManufacturer(hardwareDevice.getManufacturer())
         .deviceModel(hardwareDevice.getModel())
         .deviceSerialNumber(hardwareDevice.getSerialNumber())
         .deviceActivationCode(hardwareDevice.getActivationCode())
         .build();
   }

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

   public enum ActivationLetterOrderFailure {
      ACCOUNT_PERMANENTLY_DISABLED,
      NOT_CONFIGURED,
      ORDER_ALREADY_EXISTS,
      ORDER_NOT_FOUND;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class Airlock2FAAdminServiceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Airlock2FAUserAccountRepository accountRepository;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Airlock2FAActivationLetterService> activationLetterService;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Airlock2FAFindActivationLetterOrderService> findActivationOrderService;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Airlock2FAActivationLetterOrderService> activationOrderService;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Airlock2FAActivationLetterPrintingService> activationLetterPrintingService;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Airlock2FAShipmentLetterPrintingService> shipmentLetterPrintingService;
      @SuppressFBWarnings(justification = "generated code")
      private CurrentAdministrator administrator;
      @SuppressFBWarnings(justification = "generated code")
      private SigningLogger signingLogger;
      @SuppressFBWarnings(justification = "generated code")
      private HelpdeskLogger helpdeskLogger;

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

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAdminService.Airlock2FAAdminServiceBuilder accountRepository(@NonNull Airlock2FAUserAccountRepository accountRepository) {
         if (accountRepository == null) {
            throw new NullPointerException("accountRepository is marked non-null but is null");
         }

         this.accountRepository = accountRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAdminService.Airlock2FAAdminServiceBuilder activationLetterService(
         @NonNull Optional<Airlock2FAActivationLetterService> activationLetterService
      ) {
         if (activationLetterService == null) {
            throw new NullPointerException("activationLetterService is marked non-null but is null");
         }

         this.activationLetterService = activationLetterService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAdminService.Airlock2FAAdminServiceBuilder findActivationOrderService(
         @NonNull Optional<Airlock2FAFindActivationLetterOrderService> findActivationOrderService
      ) {
         if (findActivationOrderService == null) {
            throw new NullPointerException("findActivationOrderService is marked non-null but is null");
         }

         this.findActivationOrderService = findActivationOrderService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAdminService.Airlock2FAAdminServiceBuilder activationOrderService(
         @NonNull Optional<Airlock2FAActivationLetterOrderService> activationOrderService
      ) {
         if (activationOrderService == null) {
            throw new NullPointerException("activationOrderService is marked non-null but is null");
         }

         this.activationOrderService = activationOrderService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAdminService.Airlock2FAAdminServiceBuilder activationLetterPrintingService(
         @NonNull Optional<Airlock2FAActivationLetterPrintingService> activationLetterPrintingService
      ) {
         if (activationLetterPrintingService == null) {
            throw new NullPointerException("activationLetterPrintingService is marked non-null but is null");
         }

         this.activationLetterPrintingService = activationLetterPrintingService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAdminService.Airlock2FAAdminServiceBuilder shipmentLetterPrintingService(
         @NonNull Optional<Airlock2FAShipmentLetterPrintingService> shipmentLetterPrintingService
      ) {
         if (shipmentLetterPrintingService == null) {
            throw new NullPointerException("shipmentLetterPrintingService is marked non-null but is null");
         }

         this.shipmentLetterPrintingService = shipmentLetterPrintingService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAdminService.Airlock2FAAdminServiceBuilder administrator(@NonNull CurrentAdministrator administrator) {
         if (administrator == null) {
            throw new NullPointerException("administrator is marked non-null but is null");
         }

         this.administrator = administrator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAdminService.Airlock2FAAdminServiceBuilder signingLogger(@NonNull SigningLogger signingLogger) {
         if (signingLogger == null) {
            throw new NullPointerException("signingLogger is marked non-null but is null");
         }

         this.signingLogger = signingLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAdminService.Airlock2FAAdminServiceBuilder helpdeskLogger(@NonNull HelpdeskLogger helpdeskLogger) {
         if (helpdeskLogger == null) {
            throw new NullPointerException("helpdeskLogger is marked non-null but is null");
         }

         this.helpdeskLogger = helpdeskLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAdminService build() {
         return new Airlock2FAAdminService(
            this.accountRepository,
            this.activationLetterService,
            this.findActivationOrderService,
            this.activationOrderService,
            this.activationLetterPrintingService,
            this.shipmentLetterPrintingService,
            this.administrator,
            this.signingLogger,
            this.helpdeskLogger
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Airlock2FAAdminService.Airlock2FAAdminServiceBuilder(accountRepository="
            + this.accountRepository
            + ", activationLetterService="
            + this.activationLetterService
            + ", findActivationOrderService="
            + this.findActivationOrderService
            + ", activationOrderService="
            + this.activationOrderService
            + ", activationLetterPrintingService="
            + this.activationLetterPrintingService
            + ", shipmentLetterPrintingService="
            + this.shipmentLetterPrintingService
            + ", administrator="
            + this.administrator
            + ", signingLogger="
            + this.signingLogger
            + ", helpdeskLogger="
            + this.helpdeskLogger
            + ")";
      }
   }

   public enum CreateLetterFailure {
      ACCOUNT_PERMANENTLY_DISABLED,
      LETTER_NOT_CONFIGURED;
   }

   public enum DeleteLetterFailure {
      LETTERS_NOT_CONFIGURED,
      LETTER_NOT_FOUND,
      FAILED_TO_DELETE;
   }
}
