package com.airlock.iam.admin.application.configuration.vasco;

import com.airlock.iam.common.api.domain.model.user.PersistentUser;
import com.airlock.iam.common.api.domain.service.email.EmailService;
import com.airlock.iam.common.api.domain.service.email.EmailServiceException;
import com.airlock.iam.core.misc.impl.token.VascoToken;
import com.airlock.iam.core.misc.persistency.UserPersister;
import com.airlock.iam.core.misc.persistency.token.TokenAssignment;
import com.airlock.iam.core.misc.renderer.util.TokenOrderInfoUtils;
import com.airlock.iam.core.misc.util.report.ReportRenderer;
import com.airlock.iam.core.misc.util.report.ReportRendererException;
import com.airlock.iam.log.usertrail.UserTrailLogger;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import com.google.common.base.Strings;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.time.FastDateFormat;

@Plugin(description = "Configuration settings for the generation of Vasco letters.")
public class VascoLetterGenerator {
   private static final FastDateFormat LETTER_DATE_FORMAT = FastDateFormat.getInstance("dd.MM.yyyy");
   private static final String SERIAL_ID_PARAM = "serial-id";
   @PluginProperty(description = "Tells this task which generic renderer to use to render reports.", optional = false)
   private ReportRenderer reportRenderer;
   @StringProperty(
      description = "Filename suffix for rendered report files. The indicated suffix is appended to the generated reports. This may be required if the files are processed (e.g. printed) by another process (manual or automatic). ",
      optional = true,
      suggestedValues = {".pdf", ".txt"}
   )
   private String fileNameSuffix;
   @PluginProperty(description = "The user persister plug-in used to load and store user information. ", optional = false)
   private UserPersister userPersister;
   @StringProperty(description = "The recipient's email address.", exampleValues = {"airlock@yourcompany.com", "authserver@intranet.net"}, optional = false)
   private String recipientEmailAddress;
   private static final String RECIPIENT_EMAIL_ADDRESS_FIELD = "recipientEmailAddress";
   @StringProperty(
      description = "Tells the report renderer which attribute in the context data container contains the language to be used. If this property is configured and if the context data container of the user has a value for this attribute, it is used when calling the report renderer plug-in.",
      optional = true,
      suggestedValues = "language"
   )
   private String languageAttributeName;
   @StringProperty(description = "The subject of the email to be sent.", isMultiLineText = false, optional = true, defaultValue = "Vasco Token activation")
   private String emailSubject;
   @StringProperty(
      description = "The body of the email to be sent.",
      isMultiLineText = true,
      optional = true,
      defaultValue = "Please refer to the attached document for token details."
   )
   private String emailBody;
   @PluginProperty(
      description = "Email service plugin. This defines what mail server is used for sending the email. It also defines the sender address and whether the email should be signed or not.",
      optional = false
   )
   private EmailService emailService;

   public void renderAndSendReport(VascoToken token, String username) throws IOException, EmailServiceException {
      Objects.requireNonNull(token, "Token must not be null.");
      Objects.requireNonNull(username, "Username must not be null.");

      PersistentUser user;
      try {
         user = this.userPersister.getPersistentUserByName(username);
      } catch (Exception e) {
         throw new IOException("Error while retrieving user.", e);
      }

      Map<String, Object> parameters = new HashMap<>();
      parameters.putAll(user.getContextData());
      parameters.put("username", user.getName());
      parameters.put("DateString", LETTER_DATE_FORMAT.format(new Date()));
      parameters.put("serial-id", token.getSerialId());
      TokenAssignment ta = token.getAssignmentForUser(user.getName());
      TokenOrderInfoUtils.addOrderInfo(parameters, ta.getOrderNewUser(), ta.getOrderNewDate());
      String language;
      if (this.languageAttributeName != null) {
         Object o = user.getContextDataValue(this.languageAttributeName);
         language = o == null ? null : o.toString();
      } else {
         language = null;
      }

      Path file = Files.createTempFile("vascoTokenLetter", this.fileNameSuffix());

      try (OutputStream out = Files.newOutputStream(file)) {
         this.reportRenderer.renderReport(language, parameters, out);
         String shortDesc = "Vasco letter";
         UserTrailLogger.log(user.getName(), "Generated " + shortDesc + ".");
         this.emailService
            .sendEmail(List.of(new InternetAddress(this.recipientEmailAddress)), List.of(), List.of(), this.emailSubject, this.emailBody, false, file.toFile());
         UserTrailLogger.log(user.getName(), "Vasco letter successfully generated and sent as mail attachment to " + this.recipientEmailAddress + ".");
      } catch (ReportRendererException e) {
         throw new IOException("Error while rendering report.", e);
      } catch (AddressException e) {
         throw new EmailServiceException("Error while sending email.", e);
      } finally {
         FileUtils.deleteQuietly(file.toFile());
      }
   }

   private String fileNameSuffix() {
      return Strings.isNullOrEmpty(this.fileNameSuffix)
         ? this.reportRenderer.fileNameSuffix()
         : org.apache.commons.lang3.Strings.CS.prependIfMissing(this.fileNameSuffix, ".", new CharSequence[0]);
   }

   @ConfigurationValidator
   public static void validateConfig(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment) throws ConfigurationException {
      checkEmailAddress(config, env, context);
   }

   private static void checkEmailAddress(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws ConfigurationException {
      try {
         new InternetAddress(StringPropertyValueReader.effectiveStringConfigValue(config, "recipientEmailAddress", env, context));
      } catch (AddressException e) {
         throw new PropertyConstraintViolationException("recipientEmailAddress", config, env, context, "Invalid email address.", e);
      }
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public VascoLetterGenerator(
      ReportRenderer reportRenderer,
      String fileNameSuffix,
      UserPersister userPersister,
      String recipientEmailAddress,
      String languageAttributeName,
      String emailSubject,
      String emailBody,
      EmailService emailService
   ) {
      this.reportRenderer = reportRenderer;
      this.fileNameSuffix = fileNameSuffix;
      this.userPersister = userPersister;
      this.recipientEmailAddress = recipientEmailAddress;
      this.languageAttributeName = languageAttributeName;
      this.emailSubject = emailSubject;
      this.emailBody = emailBody;
      this.emailService = emailService;
   }

   @SuppressFBWarnings(justification = "generated code")
   public VascoLetterGenerator() {
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class VascoLetterGeneratorBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ReportRenderer reportRenderer;
      @SuppressFBWarnings(justification = "generated code")
      private String fileNameSuffix;
      @SuppressFBWarnings(justification = "generated code")
      private UserPersister userPersister;
      @SuppressFBWarnings(justification = "generated code")
      private String recipientEmailAddress;
      @SuppressFBWarnings(justification = "generated code")
      private String languageAttributeName;
      @SuppressFBWarnings(justification = "generated code")
      private String emailSubject;
      @SuppressFBWarnings(justification = "generated code")
      private String emailBody;
      @SuppressFBWarnings(justification = "generated code")
      private EmailService emailService;

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

      @SuppressFBWarnings(justification = "generated code")
      public VascoLetterGenerator.VascoLetterGeneratorBuilder reportRenderer(ReportRenderer reportRenderer) {
         this.reportRenderer = reportRenderer;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoLetterGenerator.VascoLetterGeneratorBuilder fileNameSuffix(String fileNameSuffix) {
         this.fileNameSuffix = fileNameSuffix;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoLetterGenerator.VascoLetterGeneratorBuilder userPersister(UserPersister userPersister) {
         this.userPersister = userPersister;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoLetterGenerator.VascoLetterGeneratorBuilder recipientEmailAddress(String recipientEmailAddress) {
         this.recipientEmailAddress = recipientEmailAddress;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoLetterGenerator.VascoLetterGeneratorBuilder languageAttributeName(String languageAttributeName) {
         this.languageAttributeName = languageAttributeName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoLetterGenerator.VascoLetterGeneratorBuilder emailSubject(String emailSubject) {
         this.emailSubject = emailSubject;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoLetterGenerator.VascoLetterGeneratorBuilder emailBody(String emailBody) {
         this.emailBody = emailBody;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoLetterGenerator.VascoLetterGeneratorBuilder emailService(EmailService emailService) {
         this.emailService = emailService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public VascoLetterGenerator build() {
         return new VascoLetterGenerator(
            this.reportRenderer,
            this.fileNameSuffix,
            this.userPersister,
            this.recipientEmailAddress,
            this.languageAttributeName,
            this.emailSubject,
            this.emailBody,
            this.emailService
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "VascoLetterGenerator.VascoLetterGeneratorBuilder(reportRenderer="
            + this.reportRenderer
            + ", fileNameSuffix="
            + this.fileNameSuffix
            + ", userPersister="
            + this.userPersister
            + ", recipientEmailAddress="
            + this.recipientEmailAddress
            + ", languageAttributeName="
            + this.languageAttributeName
            + ", emailSubject="
            + this.emailSubject
            + ", emailBody="
            + this.emailBody
            + ", emailService="
            + this.emailService
            + ")";
      }
   }
}
