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

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.oauth2.api.domain.model.AuthorizationServerId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2Scope;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import lombok.NonNull;

public record OAuth2ExtendedSession(
   @NonNull OAuth2SessionId id,
   @NonNull OAuth2ClientId clientId,
   @NonNull Set<OAuth2Scope> scopes,
   @NonNull Instant authenticationTimestamp,
   @NonNull AuthorizationServerId authorizationServerId,
   @NonNull OAuth2SessionClaims claims,
   Set<OAuth2CustomSessionAttribute> customAttributes
) {
   public OAuth2ExtendedSession {
      if (id == null) {
         throw new NullPointerException("id is marked non-null but is null");
      }

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

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

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

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

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

      customAttributes = customAttributes != null ? new HashSet<>(customAttributes) : new HashSet<>();
   }

   public boolean hasCustomAttribute(String name) {
      return this.customAttributes.stream().anyMatch(attribute -> attribute.getName().equals(name));
   }

   public Set<OAuth2CustomSessionAttribute> customAttributes() {
      return Set.copyOf(this.customAttributes);
   }

   public void addCustomAttributes(Set<OAuth2CustomSessionAttribute> newAttributes) {
      newAttributes.stream()
         .filter(attribute -> this.hasCustomAttribute(attribute.getName()))
         .findAny()
         .ifPresent(
            attribute -> {
               throw new ViolationException(
                  String.format("Error adding already present OAuth 2.0 session attribute with name %s", LogUtil.logSafeQuote(attribute.getName()))
               );
            }
         );
      this.customAttributes.addAll(newAttributes);
   }

   public void deleteCustomAttributes(Set<String> attributeNames) {
      attributeNames.forEach(this::deleteCustomAttribute);
   }

   private void deleteCustomAttribute(String attributeName) {
      boolean isRemoved = this.customAttributes.removeIf(attribute -> attribute.getName().equals(attributeName));
      if (!isRemoved) {
         throw new ViolationException(String.format("Error deleting nonexistent OAuth 2.0 session attribute with name %s", LogUtil.logSafeQuote(attributeName)));
      }
   }

   @Override
   public boolean equals(Object o) {
      return o != null && this.getClass() == o.getClass() ? Objects.equals(this.id, ((OAuth2ExtendedSession)o).id) : false;
   }

   @Override
   public int hashCode() {
      return Objects.hashCode(this.id);
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2ExtendedSessionBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2SessionId id;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2ClientId clientId;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<OAuth2Scope> scopes;
      @SuppressFBWarnings(justification = "generated code")
      private Instant authenticationTimestamp;
      @SuppressFBWarnings(justification = "generated code")
      private AuthorizationServerId authorizationServerId;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2SessionClaims claims;
      @SuppressFBWarnings(justification = "generated code")
      private Set<OAuth2CustomSessionAttribute> customAttributes;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ExtendedSession.OAuth2ExtendedSessionBuilder id(@NonNull OAuth2SessionId id) {
         if (id == null) {
            throw new NullPointerException("id is marked non-null but is null");
         }

         this.id = id;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ExtendedSession.OAuth2ExtendedSessionBuilder clientId(@NonNull OAuth2ClientId clientId) {
         if (clientId == null) {
            throw new NullPointerException("clientId is marked non-null but is null");
         }

         this.clientId = clientId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ExtendedSession.OAuth2ExtendedSessionBuilder scope(OAuth2Scope scope) {
         if (this.scopes == null) {
            this.scopes = new ArrayList<>();
         }

         this.scopes.add(scope);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ExtendedSession.OAuth2ExtendedSessionBuilder scopes(Collection<? extends OAuth2Scope> scopes) {
         if (scopes == null) {
            throw new NullPointerException("scopes cannot be null");
         }

         if (this.scopes == null) {
            this.scopes = new ArrayList<>();
         }

         this.scopes.addAll(scopes);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ExtendedSession.OAuth2ExtendedSessionBuilder clearScopes() {
         if (this.scopes != null) {
            this.scopes.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ExtendedSession.OAuth2ExtendedSessionBuilder authenticationTimestamp(@NonNull Instant authenticationTimestamp) {
         if (authenticationTimestamp == null) {
            throw new NullPointerException("authenticationTimestamp is marked non-null but is null");
         }

         this.authenticationTimestamp = authenticationTimestamp;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ExtendedSession.OAuth2ExtendedSessionBuilder authorizationServerId(@NonNull AuthorizationServerId authorizationServerId) {
         if (authorizationServerId == null) {
            throw new NullPointerException("authorizationServerId is marked non-null but is null");
         }

         this.authorizationServerId = authorizationServerId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ExtendedSession.OAuth2ExtendedSessionBuilder claims(@NonNull OAuth2SessionClaims claims) {
         if (claims == null) {
            throw new NullPointerException("claims is marked non-null but is null");
         }

         this.claims = claims;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ExtendedSession.OAuth2ExtendedSessionBuilder customAttributes(Set<OAuth2CustomSessionAttribute> customAttributes) {
         this.customAttributes = customAttributes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ExtendedSession build() {
         return new OAuth2ExtendedSession(
            this.id,
            this.clientId,
            switch (this.scopes == null ? 0 : this.scopes.size()) {
               case 0 -> Collections.emptySet();
               case 1 -> Collections.singleton(this.scopes.get(0));
               default -> {
                  Set<OAuth2Scope> scopes = new LinkedHashSet<>(
                     this.scopes.size() < 1073741824 ? 1 + this.scopes.size() + (this.scopes.size() - 3) / 3 : Integer.MAX_VALUE
                  );
                  scopes.addAll(this.scopes);
                  yield Collections.unmodifiableSet(scopes);
               }
            },
            this.authenticationTimestamp,
            this.authorizationServerId,
            this.claims,
            this.customAttributes
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2ExtendedSession.OAuth2ExtendedSessionBuilder(id="
            + this.id
            + ", clientId="
            + this.clientId
            + ", scopes="
            + this.scopes
            + ", authenticationTimestamp="
            + this.authenticationTimestamp
            + ", authorizationServerId="
            + this.authorizationServerId
            + ", claims="
            + this.claims
            + ", customAttributes="
            + this.customAttributes
            + ")";
      }
   }
}
