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

import com.airlock.iam.admin.api.domain.model.administrator.CurrentAdministrator;
import com.airlock.iam.admin.domain.model.token.vasco.VascoOtpDevice;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.misc.data.OptionalValue;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.infrastructure.exceptions.ValidationException;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.log.api.signing.SigningLogger;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import java.util.Date;
import java.util.List;
import java.util.Optional;

public class VascoOtpDeviceManagementService {
   private static final ELogger LOG = ELoggerFactory.getLogger(VascoOtpDeviceManagementService.class);
   private final VascoOtpDeviceRepository deviceRepo;
   private final CurrentAdministrator admin;
   private final SigningLogger signingLogger;

   @Inject
   public VascoOtpDeviceManagementService(VascoOtpDeviceRepository deviceRepo, CurrentAdministrator admin, SigningLogger signingLogger) {
      this.deviceRepo = deviceRepo;
      this.admin = admin;
      this.signingLogger = signingLogger;
   }

   public Optional<VascoOtpDevice> findDeviceById(VascoOtpDeviceId deviceId) {
      return this.deviceRepo.findDeviceById(deviceId);
   }

   public List<VascoOtpDevice> getFilteredDevices(VascoOtpDeviceFilter filter) {
      return this.deviceRepo.getFilteredDevices(filter);
   }

   public void enableDevice(VascoOtpDeviceId deviceId) {
      VascoOtpDevice device = this.deviceRepo.getDeviceById(deviceId);
      if (!device.isEnabled()) {
         device.enable();
         this.deviceRepo.saveDevice(device);
         this.logAdminAction("enabled Vasco device " + deviceId);
      } else {
         LOG.debug("Not enabling device because it is already enabled.");
      }
   }

   public void disableDevice(VascoOtpDeviceId deviceId) {
      VascoOtpDevice device = this.deviceRepo.getDeviceById(deviceId);
      if (device.isEnabled()) {
         device.disable();
         this.deviceRepo.saveDevice(device);
         this.logAdminAction("disabled Vasco device " + deviceId);
      } else {
         LOG.debug("Not disabling device because it is already disabled.");
      }
   }

   public boolean synchronizeDevice(VascoOtpDeviceId deviceId, String otp1, String otp2) {
      boolean success = this.deviceRepo.synchronizeDevice(deviceId, otp1, otp2);
      if (success) {
         this.logAdminAction("successfully synchronized Vasco OTP device " + deviceId);
      } else {
         this.logAdminAction("failed to synchronize Vasco OTP device " + deviceId);
      }

      return success;
   }

   public void deleteDevice(VascoOtpDeviceId deviceId) {
      VascoOtpDevice device = this.deviceRepo.getDeviceById(deviceId);
      if (device.isAssigned()) {
         throw new ViolationException("Cannot delete Vasco OTP device that is still assigned");
      }

      this.deviceRepo.deleteDevice(deviceId);
      this.logAdminAction("deleted Vasco OTP device " + deviceId);
   }

   public ValidationResult validateDeviceValidityUpdate(VascoOtpDeviceId deviceId, OptionalValue<Date> validFrom, OptionalValue<Date> validTo) {
      try {
         VascoOtpDevice device = this.deviceRepo.getDeviceById(deviceId);
         this.tryUpdateDeviceValidity(device, validFrom, validTo);
      } catch (ValidationException e) {
         return ValidationResult.invalid(e.getErrors());
      }

      return ValidationResult.valid();
   }

   public void updateDeviceValidity(VascoOtpDeviceId deviceId, OptionalValue<Date> validFrom, OptionalValue<Date> validTo) {
      VascoOtpDevice device = this.deviceRepo.getDeviceById(deviceId);
      this.tryUpdateDeviceValidity(device, validFrom, validTo);
      this.deviceRepo.saveDevice(device);
      this.logAdminAction("updated validity of Vasco device " + deviceId + ": new validity " + this.validity(device));
   }

   private void tryUpdateDeviceValidity(VascoOtpDevice device, OptionalValue<Date> validFrom, OptionalValue<Date> validTo) {
      Date oldValidFrom = device.getValidFrom().orElse(null);
      Date oldValidTo = device.getValidTo().orElse(null);
      device.updateValidity(Optional.ofNullable((Date)validFrom.orElse(oldValidFrom)), Optional.ofNullable((Date)validTo.orElse(oldValidTo)));
   }

   private String validity(VascoOtpDevice device) {
      return "from " + device.getValidFrom().map(Date::toString).orElse("-") + " to " + device.getValidTo().map(Date::toString).orElse("-");
   }

   private void logAdminAction(String actionDesc) {
      this.signingLogger.log("Admin " + LogUtil.quote(this.admin.getName()) + " " + actionDesc);
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class VascoOtpDeviceManagementServiceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private VascoOtpDeviceRepository deviceRepo;
      @SuppressFBWarnings(justification = "generated code")
      private CurrentAdministrator admin;
      @SuppressFBWarnings(justification = "generated code")
      private SigningLogger signingLogger;

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

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDeviceManagementService.VascoOtpDeviceManagementServiceBuilder deviceRepo(VascoOtpDeviceRepository deviceRepo) {
         this.deviceRepo = deviceRepo;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDeviceManagementService.VascoOtpDeviceManagementServiceBuilder admin(CurrentAdministrator admin) {
         this.admin = admin;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDeviceManagementService.VascoOtpDeviceManagementServiceBuilder signingLogger(SigningLogger signingLogger) {
         this.signingLogger = signingLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDeviceManagementService build() {
         return new VascoOtpDeviceManagementService(this.deviceRepo, this.admin, this.signingLogger);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "VascoOtpDeviceManagementService.VascoOtpDeviceManagementServiceBuilder(deviceRepo="
            + this.deviceRepo
            + ", admin="
            + this.admin
            + ", signingLogger="
            + this.signingLogger
            + ")";
      }
   }
}
