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

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.model.phonenumber.PhoneNumber;
import com.airlock.iam.common.domain.model.sms.SmsMessageTemplate;
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.PhoneNumbersProvider;
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.List;

public final class SmsEventSubscriber implements DomainEventSubscriber<DomainEvent> {
   private static final ELogger LOG = ELoggerFactory.getLogger(SmsEventSubscriber.class);
   private final Provider<SubscribedEvent> subscribedEvent;
   private final Provider<List<PhoneNumbersProvider>> phoneNumbersProviders;
   private final Provider<DomainEventMessageProvider> messageProvider;
   private final Provider<SmsMessageTemplate> smsMessageTemplate;
   private final Provider<SmsSendingService> smsService;
   private final Provider<AsynchronousExecutor> executor;

   @Inject
   SmsEventSubscriber(
      Provider<SubscribedEvent> subscribedEvent,
      Provider<List<PhoneNumbersProvider>> phoneNumbersProviders,
      Provider<DomainEventMessageProvider> messageProvider,
      Provider<SmsMessageTemplate> smsMessageTemplate,
      Provider<SmsSendingService> smsService,
      Provider<AsynchronousExecutor> executor
   ) {
      this.subscribedEvent = subscribedEvent;
      this.phoneNumbersProviders = phoneNumbersProviders;
      this.messageProvider = messageProvider;
      this.smsMessageTemplate = smsMessageTemplate;
      this.smsService = smsService;
      this.executor = executor;
   }

   @Override
   public void handleEvent(DomainEvent event) {
      if (((SubscribedEvent)this.subscribedEvent.get()).canHandle(event)) {
         List<PhoneNumber> phoneNumbers = ((List)this.phoneNumbersProviders.get())
            .stream()
            .flatMap(phoneNumbersProvider -> phoneNumbersProvider.phoneNumbers(event).stream())
            .toList();
         if (phoneNumbers.isEmpty()) {
            LOG.info(String.format("Not sending any SMS for event %s because no valid phone number was provided", LogUtil.quote(event.displayName())));
            return;
         }

         SmsMessageTemplate template = (SmsMessageTemplate)this.smsMessageTemplate.get();
         String message = ((DomainEventMessageProvider)this.messageProvider.get()).translate(event, template.getKeyForMessage());
         ((AsynchronousExecutor)this.executor.get())
            .execute(new SmsEventSubscriber.AsyncRunnable(event, phoneNumbers, template, message, (SmsSendingService)this.smsService.get()));
      }
   }

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

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

   private static final class AsyncRunnable implements Runnable {
      private final DomainEvent event;
      private final List<PhoneNumber> phoneNumbers;
      private final SmsMessageTemplate template;
      private final String message;
      private final SmsSendingService smsService;

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

      private void sendSms() {
         this.phoneNumbers
            .forEach(
               number -> {
                  try {
                     this.smsService.sendSms(this.template.getOriginator(), number, this.message);
                     SmsEventSubscriber.LOG.info("Successfully sent notification SMS for " + DomainEventUtils.describeEvent(this.event));
                  } catch (RuntimeException e) {
                     SmsEventSubscriber.LOG
                        .warn(
                           LogTypes.SMS_GATEWAY,
                           String.format("Failed to send notification SMS for event %s. Cause: %s", LogUtil.quote(this.event.displayName()), e.getMessage())
                        );
                  }
               }
            );
      }

      @SuppressFBWarnings(justification = "generated code")
      public AsyncRunnable(DomainEvent event, List<PhoneNumber> phoneNumbers, SmsMessageTemplate template, String message, SmsSendingService smsService) {
         this.event = event;
         this.phoneNumbers = phoneNumbers;
         this.template = template;
         this.message = message;
         this.smsService = smsService;
      }
   }
}
