package com.airlock.iam.servicecontainer.app.infrastructure.webapp.model;

import com.airlock.iam.common.domain.service.sso.AcceptedSsoTicketService;
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.AES256DecryptionTicketDecoder;
import com.airlock.iam.core.misc.util.ticket.codec.AES256DecryptionTicketDecoder.AES256DecryptionTicketDecoderBuilder;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.elog.api.LogTypes;
import com.airlock.iam.plugin.framework.application.service.builder.plugin.PluginPropertyBuilderInitializer;
import com.airlock.iam.servicecontainer.app.application.configuration.Server;
import com.airlock.iam.servicecontainer.app.application.configuration.Service;
import com.airlock.iam.servicecontainer.app.infrastructure.server.ManagableViaGUI;
import com.airlock.iam.servicecontainer.app.infrastructure.webapp.presenter.GenericServicePresenter;
import com.airlock.iam.servicecontainer.app.infrastructure.webapp.presenter.ServicePresenter;
import com.airlock.iam.servicecontainer.app.infrastructure.webapp.presenter.ServicePresenterFactory;
import com.airlock.iam.servicecontainer.app.infrastructure.webapp.view.GenericServiceView;
import com.vaadin.server.VaadinSession;
import java.util.Map;

public class ServiceContainerModel {
   private static final ELogger LOG = ELoggerFactory.getLogger(ServiceContainerModel.class);
   private final Map<String, ServicePresenter<?, ?>> servicesToBePresented;

   public ServiceContainerModel(Map<String, ServicePresenter<?, ?>> servicesToBePresented) {
      this.servicesToBePresented = servicesToBePresented;
   }

   public void authenticateUserWithSsoTicket(String encryptedSsoTicketString) throws ServiceContainerException {
      AcceptedSsoTicketService acceptedSsoTicketService = this.getServer().getAcceptedSsoTicketsService();
      Ticket ticket = this.decryptTicket(encryptedSsoTicketString);
      if (ticket.hasTicketExpired()) {
         String errorMessage = "The single sign on ticket used to access the Service Container webapplication has expired.";
         LOG.error(LogTypes.SERVER_INFO_SYS, errorMessage);
         throw new TicketExpiredException(errorMessage);
      }

      if (acceptedSsoTicketService.alreadyAccepted(ticket)) {
         String errorMessage = "The single sign on ticket used to access the Service Container webapplication was used before.";
         LOG.error(LogTypes.SERVER_INFO_SYS, errorMessage);
         throw new TicketAlreadyUsedException(errorMessage);
      }

      acceptedSsoTicketService.accept(ticket);
      VaadinSession.getCurrent()
         .setAttribute("GSID", ticket.getFirstValueForName("GSID").orElseThrow(() -> new UnknownServiceContainerException("No GSID in ticket")));
      VaadinSession.getCurrent()
         .setAttribute("username", ticket.getFirstValueForName("username").orElseThrow(() -> new UnknownServiceContainerException("No username in ticket")));
      VaadinSession.getCurrent().setAttribute("loggedIn", true);
   }

   private Ticket decryptTicket(String encryptedSsoTicketString) throws DecryptionFailedException {
      AES256DecryptionTicketDecoder decoder = PluginPropertyBuilderInitializer.builder(AES256DecryptionTicketDecoderBuilder.class)
         .maxPBKDF2Iterations(32000)
         .requireAuthenticatedEncryption(false)
         .password(this.getServer().getServiceContainerSharedSecret())
         .build();

      try {
         return decoder.decode(encryptedSsoTicketString, "UTF-8");
      } catch (TicketEncodingException e) {
         LOG.error(
            LogTypes.SERVER_INFO_SYS,
            "The single sign on ticket could not be decrypted. Either the shared secrets (Service Container Shared Secret) between the admin tool and the service container are not equal or the sso ticket is corrupt. Please edit the property Service Container Shared Secret under Admin Tool and Service Container within the configuration and make sure they are equal."
         );
         throw new DecryptionFailedException("The single sign on ticket could not be decrypted.", e);
      }
   }

   public boolean isUserAlreadyLoggedIn() {
      Boolean sessionLoggedIn = (Boolean)VaadinSession.getCurrent().getAttribute("loggedIn");
      return sessionLoggedIn != null && sessionLoggedIn;
   }

   public void createServicePresenters() {
      this.servicesToBePresented.clear();
      LOG.debug("Initializing " + this.getServer().getServices().length + " servicePresenters.");

      for (Service service : this.getServer().getServices()) {
         ServicePresenter<?, ?> servicePresenter;
         if (service instanceof ManagableViaGUI) {
            Class<? extends ServicePresenter<?, ?>> servicePresenterClass = ((ManagableViaGUI)service).getPresenterClass();
            LOG.info(LogTypes.SERVER_INFO_SYS, "service: " + service.getName() + ", presenter: " + servicePresenterClass);
            servicePresenter = ServicePresenterFactory.create(servicePresenterClass, service);
         } else {
            LOG.info(LogTypes.SERVER_INFO_SYS, "service: " + service.getName() + ", presenter: " + GenericServicePresenter.class);
            servicePresenter = ServicePresenterFactory.create(GenericServicePresenter.class, service);
         }

         this.servicesToBePresented.put(service.getRuntimeIdentifier(), servicePresenter);
      }
   }

   public Map<String, ServicePresenter<?, ?>> getServicesToBePresented() {
      return this.servicesToBePresented;
   }

   public GenericServiceView getViewForServicePresenter(String serviceName) {
      return this.servicesToBePresented.get(serviceName) != null ? this.servicesToBePresented.get(serviceName).getView() : null;
   }

   public void initServicePresenters() {
      for (ServicePresenter<?, ?> servicePresenter : this.servicesToBePresented.values()) {
         servicePresenter.getView().init();
      }
   }

   public Server getServer() {
      return Server.get();
   }
}
