package com.airlock.iam.admin.infrastructure.session;

import com.airlock.iam.admin.domain.model.session.AdminappSession;
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.mapification.MapifiedValues;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.json.JsonCoding;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.common.api.domain.model.authentication.Authentee;
import com.airlock.iam.common.api.domain.model.contextdata.ContextData;
import com.airlock.iam.common.api.domain.model.role.Role;
import com.airlock.iam.common.api.domain.model.user.ProvidedUsername;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.model.authentication.AuthenteeImpl;
import com.airlock.iam.common.domain.model.contextdata.ContextDataImpl;
import com.airlock.iam.common.domain.model.role.RolesUtil;
import com.airlock.iam.common.domain.model.session.SessionId;
import com.airlock.iam.common.domain.model.state.SessionStateId;
import com.airlock.iam.common.infrastructure.session.SessionNoncesMapifier;
import com.airlock.iam.system.version.IamVersion;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public final class AdminappSessionSerializerImpl implements AdminappSessionSerializer {
   private static final String USER_ID_KEY = "userId";
   private static final String PROVIDED_USERNAME_KEY = "providedUsername";
   private static final String ROLES_KEY = "roles";
   private static final String CONTEXT_DATA_KEY = "contextData";
   private static final String SESSION_ID_KEY = "sessionId";
   private static final String NONCES_KEY = "nonce";
   private static final String IAM_VERSION = "iamVersion";
   private final Provider<IamVersion> iamVersionProvider;

   @Inject
   public AdminappSessionSerializerImpl(Provider<IamVersion> iamVersionProvider) {
      this.iamVersionProvider = iamVersionProvider;
   }

   @Override
   public String serializeSession(AdminappSession session) {
      Map<String, Object> values = new HashMap<>();
      values.put("sessionId", session.sessionId().id());
      values.put(
         "nonce",
         SessionNoncesMapifier.mapifyNonces(session.sessionNonces().orElseThrow(() -> new FailureException("AdminappSession must always have nonces"))).asMap()
      );
      Authentee authentee = session.getAuthentee().orElseThrow(() -> new FailureException("Cannot serialize unauthenticated session"));
      values.put("userId", authentee.userIdentity().id().getValue());
      values.put("providedUsername", authentee.userIdentity().providedUsername().getValue());
      values.put("roles", RolesUtil.rolesToStringArray(authentee.roles()));
      values.put("contextData", authentee.contextData().asKeyValueMap());
      values.put("iamVersion", ((IamVersion)this.iamVersionProvider.get()).toFullVersionString());
      return JsonCoding.toJson(values);
   }

   @Override
   public Result<AdminappSession, AdminappSessionSerializer.DeserializationFailedResult> deserializeSession(SessionStateId sessionStateId, String serialized) {
      try {
         MapifiedValues values = fromJson(serialized);
         IamVersion dataVersion = IamVersion.fromString((String)values.get("iamVersion"));
         IamVersion iamVersion = (IamVersion)this.iamVersionProvider.get();
         if (dataVersion.compareTo(iamVersion) > 0) {
            return Result.failure(
               AdminappSessionSerializer.DeserializationFailedResult.sessionNotDeserializable(
                  "The serialized session with ID %s has a newer version (%s) than the current IAM version (%s). Discarding session."
                     .formatted(LogUtil.logSafeQuote(values.get("sessionId")), dataVersion.toFullVersionString(), iamVersion.toFullVersionString())
               )
            );
         }

         AdminappSession session = new AdminappSession(
            (SessionId)values.simpleValue("sessionId", SessionId::new), sessionStateId, SessionNoncesMapifier.demapifyNonces(values.getValues("nonce"))
         );
         session.authenticate(authenteeFromMap(values));
         return Result.result(session);
      } catch (RuntimeException e) {
         return Result.failure(AdminappSessionSerializer.DeserializationFailedResult.sessionNotDeserializable(e.getMessage()));
      }
   }

   private static Authentee authenteeFromMap(MapifiedValues values) {
      ProvidedUsername providedUsername = (ProvidedUsername)values.simpleValue("providedUsername", ProvidedUsername::providedUsernameOf);
      UserIdentity identity = new UserIdentity((UserId)values.simpleValue("userId", UserId::userId), providedUsername);
      Set<Role> roles = ((List)values.simpleValue("roles", v -> (List)v)).stream().<Role>map(Role::parseRole).collect(Collectors.toUnmodifiableSet());
      ContextData contextData = (ContextData)values.demapifyOptional("contextData", v -> new ContextDataImpl(v.asMap()))
         .orElseGet(ContextDataImpl::emptyContextData);
      return new AuthenteeImpl(identity, roles, contextData);
   }

   private static MapifiedValues fromJson(String json) {
      try {
         return new MapifiedValues(JsonCoding.fromJson(json));
      } catch (ViolationException e) {
         throw new FailureException(e);
      }
   }
}
