/*
 * This file is part of the Meeds project (https://meeds.io/).
 * Copyright (C) 2020 - 2022 Meeds Association contact@meeds.io
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package io.meeds.tenant.provisioning.service;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.springframework.stereotype.Component;

import org.exoplatform.commons.api.settings.SettingService;
import org.exoplatform.commons.api.settings.SettingValue;
import org.exoplatform.commons.api.settings.data.Context;
import org.exoplatform.commons.api.settings.data.Scope;
import org.exoplatform.commons.file.model.FileItem;
import org.exoplatform.commons.file.services.FileService;
import org.exoplatform.commons.utils.MailUtils;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.container.component.RequestLifeCycle;
import org.exoplatform.portal.branding.BrandingService;
import org.exoplatform.portal.branding.model.Branding;
import org.exoplatform.upload.UploadResource;
import org.exoplatform.upload.UploadService;

import io.meeds.deeds.constant.ObjectNotFoundException;

@Component
public class TenantProvisioningSettingsService {

  public static final String   EMAIL_SENDER_EMAIL_PROP          = "emailSenderEmail";

  public static final String   EMAIL_SENDER_NAME_PROP           = "emailSenderName";

  public static final String   EMAIL_SENDER_SIGNATURE_TEXT_PROP = "emailSenderSignatureText";

  public static final String   EMAIL_SENDER_SIGNATURE_LOGO_PROP = "emailSenderSignatureLogo";

  public static final String   EMAIL_ADITIONAL_RECEIVERS_PROP   = "emailAdditionalReceivers";

  public static final String   LOGO_UPLOAD_ID_PROP              = "logoUploadId";

  private static final Context CONTEXT                          = Context.GLOBAL.id("TENANT_PROVISIONING");

  private static final Scope   SCOPE                            = Scope.APPLICATION.id("TENANT_PROVISIONING");

  private static final String  SETTINGS_PROP_NAME               = "TENANT_PROVISIONING_SETTINGS";

  public static final String   FILE_API_NAME_SPACE              = "TenantProvisioning";

  public static final String   LOGO_NAME                        = "logo.png";

  private BrandingService      brandingService;

  private SettingService       settingService;

  private FileService          fileService;

  private UploadService        uploadService;

  private Map<String, Object>  tenantProvisioningSettings;

  private byte[]               emailLogo;

  public Map<String, Object> getSettings() {
    if (tenantProvisioningSettings != null) {
      return tenantProvisioningSettings;
    }

    setPortalContainerContext();

    SettingValue<?> settingValue = getSettingService().get(CONTEXT, SCOPE, SETTINGS_PROP_NAME);
    String value = settingValue == null || settingValue.getValue() == null ? null : settingValue.getValue().toString();
    if (value == null) {
      Branding brandingInformation = getBrandingService().getBrandingInformation();
      if (brandingInformation == null) {
        return Collections.emptyMap();
      } else {
        Map<String, Object> settings = new HashMap<>();
        settings.put(EMAIL_SENDER_NAME_PROP, brandingInformation.getCompanyName());
        settings.put(EMAIL_SENDER_EMAIL_PROP, MailUtils.getSenderEmail());
        settings.put(EMAIL_SENDER_SIGNATURE_TEXT_PROP, brandingInformation.getSiteName());
        return settings;
      }
    } else {
      return new JSONObject(URLDecoder.decode(value, StandardCharsets.UTF_8)).toMap();
    }
  }

  public void saveSettings(Map<String, Object> settings, String username) throws ObjectNotFoundException {
    if (settings == null) {
      throw new IllegalArgumentException("settings is mandatory");
    }

    setPortalContainerContext();

    if (settings.containsKey(LOGO_UPLOAD_ID_PROP)) {
      String uploadId = Objects.toString(settings.remove(LOGO_UPLOAD_ID_PROP), null);
      if (StringUtils.isNotBlank(uploadId)) {
        long logoFileId = updateEmailLogo(uploadId, username);
        settings.put(EMAIL_SENDER_SIGNATURE_LOGO_PROP, logoFileId);
      }
    }

    String value = new JSONObject(settings).toString();
    getSettingService().set(CONTEXT, SCOPE, SETTINGS_PROP_NAME, SettingValue.create(URLEncoder.encode(value, StandardCharsets.UTF_8)));
    this.tenantProvisioningSettings = null;
  }

  public String getSenderFullEmail() {
    Map<String, Object> settings = getSettings();
    String senderEmail = (String) settings.get(EMAIL_SENDER_EMAIL_PROP);
    String senderName = (String) settings.get(EMAIL_SENDER_NAME_PROP);
    return StringUtils.isBlank(senderName) ? senderEmail
                                           : senderName + "<" + senderEmail + ">";
  }

  public String[] getEmailBcc() {
    String bccs = (String) getSettings().get(EMAIL_ADITIONAL_RECEIVERS_PROP);
    return StringUtils.split(bccs, ",");
  }

  public String getEmailSignature() {
    String signature = (String) getSettings().get(EMAIL_SENDER_SIGNATURE_TEXT_PROP);
    return StringUtils.isBlank(signature) ? "" : signature;
  }

  public InputStream getEmailLogo() {
    return new ByteArrayInputStream(getEmailLogoData());
  }

  public byte[] getEmailLogoData() {
    if (emailLogo != null) {
      return emailLogo;
    }
    setPortalContainerContext();
    RequestLifeCycle.begin(PortalContainer.getInstance());
    try { // NOSONAR
      Map<String, Object> settings = getSettings();
      if (settings.containsKey(EMAIL_SENDER_SIGNATURE_LOGO_PROP)) {
        long fileId = Long.parseLong(settings.get(EMAIL_SENDER_SIGNATURE_LOGO_PROP).toString());
        try {
          FileItem file = getFileService().getFile(fileId);
          emailLogo = file.getAsByte();
        } catch (Exception e) {
          throw new IllegalStateException("Can't retrieve logo file with id " + fileId, e);
        }
      } else {
        emailLogo = getBrandingService().getLogo().getData();
      }
      return emailLogo;
    } finally {
      RequestLifeCycle.end();
    }
  }

  private long updateEmailLogo(String uploadId, String username) throws ObjectNotFoundException {
    UploadResource uploadResource = getUploadService().getUploadResource(uploadId);
    InputStream inputStream = null;
    if (uploadResource != null) {
      try { // NOSONAR
        inputStream = new FileInputStream(new File(uploadResource.getStoreLocation()));
      } catch (FileNotFoundException e) {
        throw new ObjectNotFoundException("Upload Resource in location " + uploadResource.getStoreLocation() + " wasn't found");
      } finally {
        getUploadService().removeUploadResource(uploadId);
      }
    }
    if (inputStream == null) {
      throw new ObjectNotFoundException("Upload Resource with id " + uploadId + " wasn't found");
    }
    try {
      FileItem fileItem;
      Long logoFileId = getLogoFileId();
      if (logoFileId == null) {
        fileItem = new FileItem(null,
                                LOGO_NAME,
                                "image/png",
                                FILE_API_NAME_SPACE,
                                (long) uploadResource.getUploadedSize(),
                                new Date(),
                                username,
                                false,
                                inputStream);
        fileItem = getFileService().writeFile(fileItem);
        logoFileId = fileItem.getFileInfo().getId();
      } else {
        fileItem = new FileItem(logoFileId,
                                LOGO_NAME,
                                "image/png",
                                FILE_API_NAME_SPACE,
                                (long) uploadResource.getUploadedSize(),
                                new Date(),
                                username,
                                false,
                                inputStream);
        getFileService().updateFile(fileItem);
      }
      this.emailLogo = null;
      return logoFileId;
    } catch (Exception e) {
      throw new IllegalStateException("Error while updating Tenant Provisioning Logo", e);
    }
  }

  private Long getLogoFileId() {
    Map<String, Object> settings = getSettings();
    if (settings.containsKey(EMAIL_SENDER_SIGNATURE_LOGO_PROP)) {
      return Long.parseLong(settings.get(EMAIL_SENDER_SIGNATURE_LOGO_PROP).toString());
    }
    return null;
  }

  private SettingService getSettingService() {
    if (settingService == null) {
      settingService = PortalContainer.getInstance().getComponentInstanceOfType(SettingService.class);
    }
    return settingService;
  }

  private BrandingService getBrandingService() {
    if (brandingService == null) {
      brandingService = PortalContainer.getInstance().getComponentInstanceOfType(BrandingService.class);
    }
    return brandingService;
  }

  private FileService getFileService() {
    if (fileService == null) {
      fileService = PortalContainer.getInstance().getComponentInstanceOfType(FileService.class);
    }
    return fileService;
  }

  private UploadService getUploadService() {
    if (uploadService == null) {
      uploadService = PortalContainer.getInstance().getComponentInstanceOfType(UploadService.class);
    }
    return uploadService;
  }

  private void setPortalContainerContext() {
    ExoContainerContext.setCurrentContainer(PortalContainer.getInstance());
  }

}
