package com.airlock.iam.admin.domain.model.token.vasco;

import com.airlock.iam.admin.application.service.token.vasco.VascoOtpDeviceId;
import com.airlock.iam.base.api.domain.model.Entity;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.api.domain.model.identity.Identity;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.common.application.service.validation.DateRangeValidator;
import com.airlock.iam.common.infrastructure.exceptions.ValidationException;
import com.airlock.iam.core.misc.util.DateRange;
import com.google.common.collect.ImmutableSet;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Date;
import java.util.Optional;
import java.util.Set;

public class VascoOtpDevice implements Entity<VascoOtpDevice> {
   private final VascoOtpDeviceId deviceId;
   private final Optional<Date> activationDate;
   private final Optional<Date> firstUsageDate;
   private final Optional<Date> lastUsageDate;
   private final int totalUsages;
   private boolean enabled;
   private Optional<Date> validFrom;
   private Optional<Date> validTo;
   private Set<VascoOtpAssignment> assignedUsers;

   public Identity<VascoOtpDevice> getId() {
      return this.deviceId;
   }

   public Set<VascoOtpAssignment> getAssignedUsers() {
      return ImmutableSet.copyOf(this.assignedUsers);
   }

   public VascoOtpDevice addValidAssignment(VascoOtpAssignment assignment) {
      ViolationException.checkNotPresent(this.findAssignment(assignment.getUserId()), "Device is already assigned to user. Call updateValidAssignment instead.");
      this.assignedUsers.add(assignment);
      return this;
   }

   public VascoOtpDevice updateValidAssignment(VascoOtpAssignment assignment) {
      Optional<VascoOtpAssignment> existingAssignment = this.findAssignment(assignment.getUserId());
      ViolationException.checkPresent(existingAssignment, "Device is not yet assigned to user. Call addValidAssignment instead");
      VascoOtpAssignment updatedAssignment = VascoOtpAssignment.builder()
         .userId(existingAssignment.get().getUserId())
         .assignedBy(existingAssignment.get().getAssignedBy())
         .assignmentDate(existingAssignment.get().getAssignmentDate())
         .comment(assignment.getComment())
         .additionalInfo(assignment.getAdditionalInfo())
         .build();
      this.assignedUsers.remove(existingAssignment.get());
      this.assignedUsers.add(updatedAssignment);
      return this;
   }

   void unassign(String userId) {
      Optional<VascoOtpAssignment> assignment = this.findAssignment(userId);
      if (assignment.isPresent()) {
         this.assignedUsers.remove(assignment.get());
      }
   }

   public void updateValidity(Optional<Date> newValidFrom, Optional<Date> newValidTo) {
      this.validateDateRange(newValidFrom, newValidTo);
      this.validFrom = newValidFrom;
      this.validTo = newValidTo;
   }

   public void enable() {
      this.enabled = true;
   }

   public void disable() {
      this.enabled = false;
   }

   public boolean isAssignedTo(String userId) {
      return this.findAssignment(userId).isPresent();
   }

   private Optional<VascoOtpAssignment> findAssignment(String userId) {
      return this.assignedUsers.stream().filter(a -> a.getUserId().equals(userId)).findAny();
   }

   public boolean isAssigned() {
      return !this.assignedUsers.isEmpty();
   }

   private void validateDateRange(Optional<Date> newValidFrom, Optional<Date> newValidTo) {
      ValidationResult result = DateRangeValidator.builder()
         .fromFieldName("validFrom")
         .toFieldName("validTo")
         .mandatoryFrom(false)
         .mandatoryTo(false)
         .build()
         .validate(new DateRange(newValidFrom.orElse(null), newValidTo.orElse(null)));
      if (!result.isValid()) {
         throw new ValidationException(result.getErrors());
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   VascoOtpDevice(
      VascoOtpDeviceId deviceId,
      Optional<Date> activationDate,
      Optional<Date> firstUsageDate,
      Optional<Date> lastUsageDate,
      int totalUsages,
      boolean enabled,
      Optional<Date> validFrom,
      Optional<Date> validTo,
      Set<VascoOtpAssignment> assignedUsers
   ) {
      this.deviceId = deviceId;
      this.activationDate = activationDate;
      this.firstUsageDate = firstUsageDate;
      this.lastUsageDate = lastUsageDate;
      this.totalUsages = totalUsages;
      this.enabled = enabled;
      this.validFrom = validFrom;
      this.validTo = validTo;
      this.assignedUsers = assignedUsers;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public VascoOtpDeviceId getDeviceId() {
      return this.deviceId;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Date> getActivationDate() {
      return this.activationDate;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Date> getFirstUsageDate() {
      return this.firstUsageDate;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Date> getLastUsageDate() {
      return this.lastUsageDate;
   }

   @SuppressFBWarnings(justification = "generated code")
   public int getTotalUsages() {
      return this.totalUsages;
   }

   @SuppressFBWarnings(justification = "generated code")
   public boolean isEnabled() {
      return this.enabled;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Date> getValidFrom() {
      return this.validFrom;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Date> getValidTo() {
      return this.validTo;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class VascoOtpDeviceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private VascoOtpDeviceId deviceId;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Date> activationDate;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Date> firstUsageDate;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Date> lastUsageDate;
      @SuppressFBWarnings(justification = "generated code")
      private int totalUsages;
      @SuppressFBWarnings(justification = "generated code")
      private boolean enabled;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Date> validFrom;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Date> validTo;
      @SuppressFBWarnings(justification = "generated code")
      private Set<VascoOtpAssignment> assignedUsers;

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

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDevice.VascoOtpDeviceBuilder deviceId(VascoOtpDeviceId deviceId) {
         this.deviceId = deviceId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDevice.VascoOtpDeviceBuilder activationDate(Optional<Date> activationDate) {
         this.activationDate = activationDate;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDevice.VascoOtpDeviceBuilder firstUsageDate(Optional<Date> firstUsageDate) {
         this.firstUsageDate = firstUsageDate;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDevice.VascoOtpDeviceBuilder lastUsageDate(Optional<Date> lastUsageDate) {
         this.lastUsageDate = lastUsageDate;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDevice.VascoOtpDeviceBuilder totalUsages(int totalUsages) {
         this.totalUsages = totalUsages;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDevice.VascoOtpDeviceBuilder enabled(boolean enabled) {
         this.enabled = enabled;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDevice.VascoOtpDeviceBuilder validFrom(Optional<Date> validFrom) {
         this.validFrom = validFrom;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDevice.VascoOtpDeviceBuilder validTo(Optional<Date> validTo) {
         this.validTo = validTo;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDevice.VascoOtpDeviceBuilder assignedUsers(Set<VascoOtpAssignment> assignedUsers) {
         this.assignedUsers = assignedUsers;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoOtpDevice build() {
         return new VascoOtpDevice(
            this.deviceId,
            this.activationDate,
            this.firstUsageDate,
            this.lastUsageDate,
            this.totalUsages,
            this.enabled,
            this.validFrom,
            this.validTo,
            this.assignedUsers
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "VascoOtpDevice.VascoOtpDeviceBuilder(deviceId="
            + this.deviceId
            + ", activationDate="
            + this.activationDate
            + ", firstUsageDate="
            + this.firstUsageDate
            + ", lastUsageDate="
            + this.lastUsageDate
            + ", totalUsages="
            + this.totalUsages
            + ", enabled="
            + this.enabled
            + ", validFrom="
            + this.validFrom
            + ", validTo="
            + this.validTo
            + ", assignedUsers="
            + this.assignedUsers
            + ")";
      }
   }
}
