package com.airlock.iam.flow.shared.application.service.recovery;

import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.common.api.domain.model.user.ProvidedUsername;
import com.airlock.iam.common.api.domain.model.user.RepresenterId;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.core.misc.util.ticket.Ticket;
import com.airlock.iam.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.tag.Tag;
import com.airlock.iam.flow.shared.application.service.recovery.tag.TagsMapper;
import com.airlock.iam.flow.shared.domain.model.session.UserSessionImpl;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public final class UserSessionFromTicket {
   private static final ELogger LOG = ELoggerFactory.getLogger(UserSessionFromTicket.class);
   private final Provider<TagsMapper> tagsMapper;

   @Inject
   public UserSessionFromTicket(Provider<TagsMapper> tagsMapper) {
      this.tagsMapper = tagsMapper;
   }

   public Optional<UserSessionImpl> map(Ticket ticket) {
      if (ticket.hasTicketExpired()) {
         LOG.info("Session ticket has expired, not restoring user session.");
         return Optional.empty();
      } else {
         Optional<String> ticketVersion = this.ticketValueForKey(ticket, "ticketVersion");
         if (ticketVersion.isEmpty()) {
            LOG.error(EDefaultLogType.GENERIC_LOG_TYPE, "Found no ticket version!");
            return Optional.empty();
         } else if (!ticketVersion.get().equals("1")) {
            LOG.error(EDefaultLogType.GENERIC_LOG_TYPE, "Found unexpected ticket version: [" + ticketVersion.get() + "], expected [1], ignoring ticket");
            return Optional.empty();
         } else {
            Optional<String> id = this.ticketValueForKey(ticket, "username");
            Optional<String> providedName = this.ticketValueForKey(ticket, "providedName");
            Optional<String> representerId = this.ticketValueForKey(ticket, "representerId");
            if (id.isEmpty()) {
               LOG.error(EDefaultLogType.GENERIC_LOG_TYPE, "Session ticket is missing the real user identity (id).");
               return Optional.empty();
            } else if (providedName.isEmpty()) {
               LOG.error(EDefaultLogType.GENERIC_LOG_TYPE, "Session ticket is missing the provided user identity (providedName).");
               return Optional.empty();
            } else {
               return this.createUserSession(ticket, id.get(), providedName.get(), representerId);
            }
         }
      }
   }

   private Optional<UserSessionImpl> createUserSession(Ticket ticket, String id, String providedName, Optional<String> representerId) {
      UserIdentity userIdentity = new UserIdentity(UserId.userId(id), ProvidedUsername.providedUsernameOf(providedName), representerId.map(RepresenterId::new));
      return this.createTags(ticket)
         .map(
            tags -> {
               UserSessionImpl session = UserSessionImpl.restoreUserSessionFrom(
                  userIdentity, UserSessionAttributesFromTicket.userSessionAttributes(ticket), (Set<Tag>)tags
               );
               LOG.info(
                  "Restored user session from ticket for user "
                     + LogUtil.quote(session.userId().orElseThrow().id())
                     + " with tags "
                     + Joiner.on(", ").join(session.getTags())
               );
               return session;
            }
         );
   }

   private Optional<Set<Tag>> createTags(Ticket ticket) {
      Result<Set<Tag>, TagsMapper.TagsMapperError> allTags = ((TagsMapper)this.tagsMapper.get()).fromTicket(ticket);
      if (allTags.isFailure()) {
         LOG.error(EDefaultLogType.GENERIC_LOG_TYPE, "Failed to read tags from ticket");
         return Optional.empty();
      }

      Set<Tag> notExpiredTags = allTags.get().stream().filter(tag -> !tag.isExpired()).collect(Collectors.toSet());
      if (LOG.isDebugEnabled() && notExpiredTags.size() != allTags.get().size()) {
         LOG.debug("Removed expired tags during session restore from ticket: " + Sets.difference(allTags.get(), notExpiredTags));
      }

      return Optional.of(notExpiredTags);
   }

   private Optional<String> ticketValueForKey(Ticket ticket, String key) {
      return ticket.getFirstValueForName(key);
   }
}
