package com.airlock.iam.admin.application.service.user;

import com.airlock.iam.admin.application.configuration.users.CsvUsersExportConfig;
import com.airlock.iam.admin.domain.model.user.CsvUsersExportSettings;
import com.airlock.iam.admin.domain.service.access.AdminappAccessController;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.misc.file.FileName;
import com.airlock.iam.common.api.domain.model.user.PersistentUser;
import com.airlock.iam.core.misc.persistency.AuthUserBean;
import com.google.common.base.Joiner;
import jakarta.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.QuoteMode;
import org.apache.commons.lang3.time.FastDateFormat;

@LogicForConfig(CsvUsersExportConfig.class)
public final class CsvUsersExporter implements UsersExporter {
   private static final String CSV_EXPORT_TYPE = "csv";
   private static final FastDateFormat CSV_TIMESTAMP_FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssXXX");
   private static final int FULL_ADDITIONAL_COLUMNS = 17;
   private static final int DEFAULT_ADDITIONAL_COLUMNS = 2;
   private final CsvUsersExportSettings settings;
   private final AdminappAccessController accessController;

   @Inject
   CsvUsersExporter(CsvUsersExportSettings settings, AdminappAccessController accessController) {
      this.settings = settings;
      this.accessController = accessController;
   }

   @Override
   public String exportType() {
      return "csv";
   }

   @Override
   public UsersExporter.UsersExportResult exportUsers(Stream<? extends PersistentUser> users) {
      CSVFormat format = CSVFormat.EXCEL
         .builder()
         .setDelimiter(this.settings.separationCharacter().charAt(0))
         .setQuoteMode(QuoteMode.ALL)
         .setHeader(this.headers())
         .get();
      StringBuilder output = new StringBuilder();

      try {
         CSVPrinter printer = new CSVPrinter(output, format);

         try {
            this.writeUsersToCsv(printer, users);
         } catch (Throwable var8) {
            try {
               printer.close();
            } catch (Throwable var7) {
               var8.addSuppressed(var7);
            }

            throw var8;
         }

         printer.close();
      } catch (IOException e) {
         throw new FailureException(e);
      }

      return this.csvExportResult(output.toString());
   }

   private UsersExporter.UsersExportResult csvExportResult(String data) {
      String dateString = FastDateFormat.getInstance("yyyyMMddHHmmss").format(new Date());
      FileName fileName = FileName.builder().elements(List.of("IamUsers", dateString)).suffix(".csv").build();
      return new UsersExporter.UsersExportResult(data, "application/csv;charset=" + this.settings.charset(), fileName.value());
   }

   private void writeUsersToCsv(CSVPrinter printer, Stream<? extends PersistentUser> users) {
      users.forEach(user -> {
         try {
            printer.printRecord(this.userData(user));
         } catch (IOException e) {
            throw new FailureException(e);
         }
      });
   }

   private String[] headers() {
      List<String> headers = new ArrayList<>(this.columns());
      headers.add("username");
      headers.add("roles");
      headers.addAll(this.settings.contextKeys());
      if (this.settings.fullExport()) {
         headers.add("failedLogins");
         headers.add("totalLogins");
         headers.add("firstLogin");
         headers.add("latestLoginAttempt");
         headers.add("latestSuccessfulLogin");
         headers.add("isLocked");
         headers.add("lockedDate");
         headers.add("lockedReason");
         headers.add("isValid");
         headers.add("validFrom");
         headers.add("validTo");
         headers.add("passwordOrdererd?");
         headers.add("passwordChangeEnforced");
         headers.add("nextEnforcedPasswordChange");
         headers.add("latestPasswordChange");
      }

      return headers.toArray(String[]::new);
   }

   private List<Object> userData(PersistentUser user) {
      List<Object> data = new ArrayList<>(this.columns());
      data.add(user.getName());
      data.add(user.getRoles() == null ? null : format(Joiner.on(",").join(user.getRoles())));

      for (String key : this.settings.contextKeys()) {
         if (this.accessController.isContextDataColumnVisible(key)) {
            data.add(format(user.getContextDataValue(key)));
         } else {
            data.add(null);
         }
      }

      if (this.settings.fullExport()) {
         AuthUserBean authUser = user instanceof AuthUserBean ? (AuthUserBean)user : null;
         data.add(user.getFailedLogins());
         data.add(user.getTotalLogins());
         data.add(format(user.getFirstLogin()));
         data.add(format(user.getLatestLoginAttempt()));
         data.add(format(user.getLatestSuccessfulLogin()));
         data.add(format(user.isLocked()));
         data.add(user.isLocked() ? format(user.getLockDate()) : null);
         data.add(user.isLocked() ? format(user.getLockReason()) : null);
         data.add(format(user.getUserValidity().isUserEnabled()));
         data.add(format(user.getUserValidity().getUserValidityPeriod().getBeginDate()));
         data.add(format(user.getUserValidity().getUserValidityPeriod().getEndDate()));
         data.add(authUser == null ? null : format(authUser.getOrderPasswordFlag()));
         data.add(authUser == null ? null : format(authUser.isPasswordChangeForced()));
         data.add(authUser == null ? null : format(authUser.getNextEnforcedChangeTimestamp()));
         data.add(authUser == null ? null : format(authUser.getLatestPasswordChangeTimestamp()));
      }

      return data;
   }

   private static String format(Object obj) {
      if (obj instanceof Date) {
         return CSV_TIMESTAMP_FORMATTER.format((Date)obj);
      } else if (obj instanceof Boolean) {
         return (Boolean)obj ? "TRUE" : "FALSE";
      } else if (obj instanceof byte[]) {
         return Base64.encodeBase64String((byte[])obj);
      } else {
         return obj != null ? obj.toString() : null;
      }
   }

   private int columns() {
      return this.settings.contextKeys().size() + this.additionalColumns();
   }

   private int additionalColumns() {
      return this.settings.fullExport() ? 17 : 2;
   }
}
