package com.airlock.iam.admin.domain.model.session;

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.common.api.domain.model.authentication.Authentee;
import com.airlock.iam.common.api.domain.model.role.Role;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.model.session.IamSession;
import com.airlock.iam.common.domain.model.session.SessionId;
import com.airlock.iam.common.domain.model.session.SessionNonces;
import com.airlock.iam.common.domain.model.state.SessionStateId;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import lombok.NonNull;

public final class AdminappSession implements IamSession {
   private final SessionId sessionId;
   private final SessionStateId sessionStateId;
   private SessionNonces nonces;
   private Optional<Authentee> authenticatedAdmin = Optional.empty();
   private boolean fresh;

   public AdminappSession(@NonNull SessionId sessionId, @NonNull SessionStateId sessionStateId, @NonNull SessionNonces nonces) {
      if (sessionId == null) {
         throw new NullPointerException("sessionId is marked non-null but is null");
      }

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

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

      this.sessionId = sessionId;
      this.sessionStateId = sessionStateId;
      this.nonces = nonces;
      this.fresh = true;
   }

   public void authenticate(Authentee admin) {
      if (this.authenticatedAdmin.isPresent() && !this.authenticatedAdmin.get().userIdentity().id().equals(admin.userIdentity().id())) {
         throw new ViolationException("Session is already authenticated for a different user: " + this.authenticatedAdmin.get().userIdentity().id());
      }

      this.authenticatedAdmin = Optional.of(admin);
   }

   public void deauthenticate() {
      this.authenticatedAdmin = Optional.empty();
   }

   public boolean isAuthenticated() {
      return this.authenticatedAdmin.isPresent();
   }

   public Set<Role> getRoles() {
      return this.authenticatedAdmin.<Set<Role>>map(Authentee::roles).orElse(Collections.emptySet());
   }

   public String getAuthenticatedAdminName() {
      return this.getAuthenticatedAdminIdentity().orElseThrow(() -> new FailureException("Session is not authenticated")).id().getValue();
   }

   public Optional<UserIdentity> getAuthenticatedAdminIdentity() {
      return this.authenticatedAdmin.map(Authentee::userIdentity);
   }

   public Optional<Authentee> getAuthentee() {
      return this.authenticatedAdmin;
   }

   public Optional<UserId> userId() {
      return this.authenticatedAdmin.map(a -> a.userIdentity().id());
   }

   public SessionId sessionId() {
      return this.sessionId;
   }

   public SessionStateId sessionStateId() {
      return this.sessionStateId;
   }

   public Optional<SessionNonces> sessionNonces() {
      return Optional.of(this.nonces);
   }

   public void setSessionNonces(SessionNonces newNonces) {
      this.nonces = newNonces;
   }

   public boolean isLockAcquired() {
      return true;
   }

   public void setNotFresh() {
      this.fresh = false;
   }

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