package com.airlock.iam.admin.app.infrastructure.servlet;

import com.airlock.iam.admin.application.configuration.Adminapp;
import com.airlock.iam.admin.domain.model.administrator.permissions.Actions;
import com.airlock.iam.admin.domain.model.session.AdminappSession;
import com.airlock.iam.base.domain.service.uri.UrlUtils;
import com.airlock.iam.common.api.domain.model.role.Role;
import com.airlock.iam.common.domain.model.role.RolesUtil;
import com.airlock.iam.common.domain.model.session.SessionId;
import com.airlock.iam.core.misc.authorization.AccessController;
import com.airlock.iam.core.misc.authorization.AccessControllerException;
import com.airlock.iam.core.misc.util.ServletUtils;
import com.airlock.iam.core.misc.util.ticket.KeyMultiValue;
import com.airlock.iam.core.misc.util.ticket.Ticket;
import com.airlock.iam.core.misc.util.ticket.TicketEncodingException;
import com.airlock.iam.core.misc.util.ticket.codec.AES256EncryptionTicketEncoder;
import com.airlock.iam.core.misc.util.ticket.codec.AES256EncryptionTicketEncoder.AES256EncryptionTicketEncoderBuilder;
import com.airlock.iam.plugin.framework.application.service.builder.plugin.PluginPropertyBuilderInitializer;
import com.airlock.iam.system.property.IamSystemProperties;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;

public class ServiceContainerServlet extends SsoRedirectServlet {
   private static final Duration EXPIRING_DURATION = Duration.ofSeconds(10L);

   @Override
   protected void performRedirect(HttpServletRequest request, HttpServletResponse response, String ssoTicket) {
      String contextPath = IamSystemProperties.getServiceContainerUrlPath().orElseThrow();
      String lang = ServletUtils.getRequestParameter(request, "lang", Adminapp.get().getLanguageSettings().getDefaultLanguage());
      String serviceContainerSsoPath = String.format(
         "%s/serviceContainer?sso=%s&lang=%s", contextPath, UrlUtils.urlEncodeUrlParameter(ssoTicket), UrlUtils.urlEncodeUrlParameter(lang)
      );
      ServletUtils.sendPlainRedirect(response, serviceContainerSsoPath);
   }

   @Override
   protected String createSsoToken(HttpServletRequest request) throws ServletException {
      AdminappSession session = this.getAdminappServletInjectorFactory().getAdminappSession();
      Set<Role> roles = session.getAuthentee().orElseThrow().roles();
      Ticket ssoTicket = createSsoTicket(this.getAdminappServletInjectorFactory().getSessionId(), session.getAuthenticatedAdminName(), roles);
      return encryptSsoTicket(ssoTicket, Adminapp.get().getServiceContainerSharedSecret());
   }

   @Override
   protected boolean isAuthorized() throws AccessControllerException {
      AdminappSession session = this.getAdminappSession();
      AccessController accessController = Adminapp.get().getAccessControl().getAccessController();
      return accessController.isActionAllowedFor(Actions.accessServiceContainer.name(), session.getAuthentee().orElseThrow());
   }

   @Override
   protected String getTargetApplicationName() {
      return "Service Container";
   }

   private static String encryptSsoTicket(Ticket ssoTicket, String sharedSecret) throws ServletException {
      try {
         if (StringUtils.isBlank(sharedSecret)) {
            throw new ServletException("Could not load shared secret to encrypt SSO ticket.");
         }

         AES256EncryptionTicketEncoder encoder = PluginPropertyBuilderInitializer.pluginBuilder(AES256EncryptionTicketEncoderBuilder.class)
            .password(sharedSecret)
            .build();
         return encoder.encode(ssoTicket, StandardCharsets.UTF_8.name());
      } catch (TicketEncodingException e) {
         throw new ServletException("Could not encrypt SSO ticket.", e);
      }
   }

   private static Ticket createSsoTicket(SessionId sessionId, String username, Set<Role> authRoles) {
      Ticket ticket = new Ticket(Instant.now().plus(EXPIRING_DURATION));
      ticket.addKeyMultiValue(new KeyMultiValue("username", username));
      ticket.addKeyMultiValue(new KeyMultiValue("roles", RolesUtil.rolesToStringArray(authRoles)));
      ticket.addKeyMultiValue(new KeyMultiValue("GSID", sessionId.id()));
      ticket.generateUniqueId();
      return ticket;
   }
}
