package com.airlock.iam.common.domain.service.email;

import com.airlock.iam.base.api.domain.model.email.EmailAddress;
import com.airlock.iam.base.api.domain.model.event.DomainEvent;
import com.airlock.iam.base.api.domain.model.event.DomainEventSubscriber;
import com.airlock.iam.base.domain.model.event.SubscribedEvent;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.async.AsynchronousExecutor;
import com.airlock.iam.common.api.domain.service.email.EmailService;
import com.airlock.iam.common.api.domain.service.email.EmailServiceException;
import com.airlock.iam.common.domain.model.email.EmailMessageTemplate;
import com.airlock.iam.common.domain.service.event.DomainEventMessageProvider;
import com.airlock.iam.common.domain.service.event.DomainEventUtils;
import com.airlock.iam.common.domain.service.event.EventEmailRecipientProvider;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.elog.api.LogTypes;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;
import lombok.NonNull;

public final class EmailEventSubscriber implements DomainEventSubscriber<DomainEvent> {
   private static final ELogger LOG = ELoggerFactory.getLogger(EmailEventSubscriber.class);
   private final Provider<SubscribedEvent> subscribedEvent;
   private final Provider<DomainEventMessageProvider> messageProvider;
   private final Provider<EmailService> emailService;
   private final Provider<EventEmailRecipientProvider> recipientAddressProvider;
   private final Provider<EmailMessageTemplate> emailMessageTemplate;
   private final Provider<AsynchronousExecutor> executor;

   @Inject
   EmailEventSubscriber(
      @NonNull Provider<SubscribedEvent> subscribedEvent,
      @NonNull Provider<EmailService> emailService,
      @NonNull Provider<EventEmailRecipientProvider> recipientAddressProvider,
      @NonNull Provider<DomainEventMessageProvider> messageProvider,
      @NonNull Provider<EmailMessageTemplate> emailMessageTemplate,
      @NonNull Provider<AsynchronousExecutor> executor
   ) {
      if (subscribedEvent == null) {
         throw new NullPointerException("subscribedEvent is marked non-null but is null");
      }

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

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

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

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

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

      this.subscribedEvent = subscribedEvent;
      this.emailService = emailService;
      this.recipientAddressProvider = recipientAddressProvider;
      this.messageProvider = messageProvider;
      this.emailMessageTemplate = emailMessageTemplate;
      this.executor = executor;
   }

   @Override
   public void handleEvent(DomainEvent event) {
      if (((SubscribedEvent)this.subscribedEvent.get()).canHandle(event)) {
         Optional<EmailAddress> recepientEmailAddress = this.recipientEmailAddress(event);
         if (recepientEmailAddress.isEmpty()) {
            LOG.info(String.format("Not sending email for event %s because no valid email address was provided", LogUtil.quote(event.displayName())));
            return;
         }

         EmailMessageTemplate template = (EmailMessageTemplate)this.emailMessageTemplate.get();
         String subject = ((DomainEventMessageProvider)this.messageProvider.get()).translate(event, template.getKeyForSubject());
         String body = ((DomainEventMessageProvider)this.messageProvider.get()).translate(event, template.getKeyForBody());
         ((AsynchronousExecutor)this.executor.get())
            .execute(
               new EmailEventSubscriber.AsyncRunnable(
                  event, subject, body, template.isSendAsHtml(), recepientEmailAddress.get(), (EmailService)this.emailService.get()
               )
            );
      }
   }

   private Optional<EmailAddress> recipientEmailAddress(DomainEvent event) {
      return ((EventEmailRecipientProvider)this.recipientAddressProvider.get()).provideRecipientAddress(event);
   }

   @Override
   public Class<DomainEvent> forType() {
      return DomainEvent.class;
   }

   @Override
   public String toString() {
      return "EmailEventSubscriber[" + this.subscribedEvent + "]";
   }

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

   private static final class AsyncRunnable implements Runnable {
      private final DomainEvent event;
      private final String subject;
      private final String body;
      private final boolean sendAsHtml;
      private final EmailAddress recepientEmailAddress;
      private final EmailService emailService;

      @Override
      public void run() {
         this.sendEmail();
      }

      private void sendEmail() {
         try {
            this.emailService.sendEmail(this.recepientEmailAddress, this.subject, this.body, this.sendAsHtml);
            EmailEventSubscriber.LOG.info("Successfully sent notification email for " + DomainEventUtils.describeEvent(this.event));
         } catch (EmailServiceException e) {
            EmailEventSubscriber.LOG
               .warn(
                  LogTypes.CONFIGURATION_ERROR,
                  String.format("Failed to send notification email for event %s. Cause: %s", LogUtil.quote(this.event.displayName()), e.getMessage())
               );
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      public AsyncRunnable(DomainEvent event, String subject, String body, boolean sendAsHtml, EmailAddress recepientEmailAddress, EmailService emailService) {
         this.event = event;
         this.subject = subject;
         this.body = body;
         this.sendAsHtml = sendAsHtml;
         this.recepientEmailAddress = recepientEmailAddress;
         this.emailService = emailService;
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class EmailEventSubscriberBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Provider<SubscribedEvent> subscribedEvent;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<EmailService> emailService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<EventEmailRecipientProvider> recipientAddressProvider;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<DomainEventMessageProvider> messageProvider;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<EmailMessageTemplate> emailMessageTemplate;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<AsynchronousExecutor> executor;

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

      @SuppressFBWarnings(justification = "generated code")
      public EmailEventSubscriber.EmailEventSubscriberBuilder subscribedEvent(@NonNull Provider<SubscribedEvent> subscribedEvent) {
         if (subscribedEvent == null) {
            throw new NullPointerException("subscribedEvent is marked non-null but is null");
         }

         this.subscribedEvent = subscribedEvent;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailEventSubscriber.EmailEventSubscriberBuilder emailService(@NonNull Provider<EmailService> emailService) {
         if (emailService == null) {
            throw new NullPointerException("emailService is marked non-null but is null");
         }

         this.emailService = emailService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailEventSubscriber.EmailEventSubscriberBuilder recipientAddressProvider(@NonNull Provider<EventEmailRecipientProvider> recipientAddressProvider) {
         if (recipientAddressProvider == null) {
            throw new NullPointerException("recipientAddressProvider is marked non-null but is null");
         }

         this.recipientAddressProvider = recipientAddressProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailEventSubscriber.EmailEventSubscriberBuilder messageProvider(@NonNull Provider<DomainEventMessageProvider> messageProvider) {
         if (messageProvider == null) {
            throw new NullPointerException("messageProvider is marked non-null but is null");
         }

         this.messageProvider = messageProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailEventSubscriber.EmailEventSubscriberBuilder emailMessageTemplate(@NonNull Provider<EmailMessageTemplate> emailMessageTemplate) {
         if (emailMessageTemplate == null) {
            throw new NullPointerException("emailMessageTemplate is marked non-null but is null");
         }

         this.emailMessageTemplate = emailMessageTemplate;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailEventSubscriber.EmailEventSubscriberBuilder executor(@NonNull Provider<AsynchronousExecutor> executor) {
         if (executor == null) {
            throw new NullPointerException("executor is marked non-null but is null");
         }

         this.executor = executor;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailEventSubscriber build() {
         return new EmailEventSubscriber(
            this.subscribedEvent, this.emailService, this.recipientAddressProvider, this.messageProvider, this.emailMessageTemplate, this.executor
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "EmailEventSubscriber.EmailEventSubscriberBuilder(subscribedEvent="
            + this.subscribedEvent
            + ", emailService="
            + this.emailService
            + ", recipientAddressProvider="
            + this.recipientAddressProvider
            + ", messageProvider="
            + this.messageProvider
            + ", emailMessageTemplate="
            + this.emailMessageTemplate
            + ", executor="
            + this.executor
            + ")";
      }
   }
}
