/*
 * 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.web.rest.utils;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.json.JSONObject;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import io.meeds.deeds.elasticsearch.model.DeedTenant;
import io.meeds.tenant.provisioning.model.EmailTemplate;
import io.meeds.tenant.provisioning.model.EmailTemplateDTO;
import io.meeds.tenant.provisioning.web.model.DeedTenantProvisioning;
import io.meeds.tenant.provisioning.web.model.DeedTenantProvisioningWebSocket;

public class EntityMapper {

  private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

  static {
    // Workaround when Jackson is defined in shared library with different
    // version and without artifact jackson-datatype-jsr310
    OBJECT_MAPPER.registerModule(new JavaTimeModule());
  }

  private EntityMapper() {
    // Util class
  }

  public static List<DeedTenantProvisioning> toRestDTOs(List<DeedTenant> deedTenants, String etherscanPrefix, String deedAddress) {
    return deedTenants.stream()
                      .map(deedTenant -> toRestDTO(deedTenant, etherscanPrefix, deedAddress))
                      .collect(Collectors.toList());
  }

  public static DeedTenantProvisioning toRestDTO(DeedTenant deedTenant, String etherscanPrefix, String deedAddress) {
    LocalDateTime date = deedTenant.getDate();
    String etherscanLink = etherscanPrefix + ("/nft/" + deedAddress + "/" + deedTenant.getNftId()).replace("//", "/");
    return new DeedTenantProvisioning(deedTenant.getNftId(),
                                      deedTenant.getCityIndex(),
                                      deedTenant.getCardType(),
                                      deedTenant.getManagerAddress(),
                                      deedTenant.getManagerEmail(),
                                      deedTenant.getStartupTransactionHash(),
                                      deedTenant.getShutdownTransactionHash(),
                                      deedTenant.getTenantProvisioningStatus(),
                                      deedTenant.getTenantStatus(),
                                      date == null ? null : ZonedDateTime.of(date, ZoneOffset.UTC),
                                      deedTenant.getProperties(),
                                      deedTenant.isCompleted(),
                                      etherscanLink);
  }

  public static DeedTenantProvisioningWebSocket toWebSocketDTO(String eventName, DeedTenant deedTenant, String etherscanPrefix, String deedAddress) {
    return new DeedTenantProvisioningWebSocket(toRestDTO(deedTenant, etherscanPrefix, deedAddress), eventName);
  }

  public static String toJsonString(Object object) {
    try {
      return OBJECT_MAPPER.writeValueAsString(object);
    } catch (JsonProcessingException e) {
      throw new IllegalStateException("Error parsing object to JSON string", e);
    }
  }

  public static <T> T toObject(Map<?, ?> properties, Class<T> clazz) {
    try {
      return OBJECT_MAPPER.readValue(new JSONObject(properties).toString(), clazz);
    } catch (JsonProcessingException e) {
      throw new IllegalStateException("Error parsing JSON string to object", e);
    }
  }

  public static Map<String, Object> toMap(Object object) {
    try {
      String objectString = OBJECT_MAPPER.writeValueAsString(object);
      JSONObject jsonObject = new JSONObject(objectString);
      return jsonObject.toMap();
    } catch (JsonProcessingException e) {
      throw new IllegalStateException("Error generating Map from object fields", e);
    }
  }

  @SuppressWarnings("unchecked")
  public static <T> T mergeObjects(T source, T newValues) {
    try {
      String obj1String = OBJECT_MAPPER.writeValueAsString(source);
      JSONObject object1 = new JSONObject(obj1String);
      String obj2String = OBJECT_MAPPER.writeValueAsString(newValues);
      JSONObject object2 = new JSONObject(obj2String);
      Set<String> newKeys = object2.keySet();
      for (String key : newKeys) {
        object1.put(key, object2.get(key));
      }
      return OBJECT_MAPPER.readValue(obj2String, (Class<? extends T>) source.getClass());
    } catch (JsonProcessingException e) {
      throw new IllegalStateException("Error merging JSON objects", e);
    }
  }

  public static EmailTemplateDTO toDTO(EmailTemplate emailTemplate) {
    return new EmailTemplateDTO(emailTemplate);
  }

  public static EmailTemplate fromDTO(EmailTemplateDTO emailTemplateDto) {
    return new EmailTemplate(emailTemplateDto.getId(),
                             emailTemplateDto.getName(),
                             emailTemplateDto.getDescription(),
                             emailTemplateDto.getMailSubject(),
                             emailTemplateDto.getAutomaticStatus(),
                             emailTemplateDto.getNotePageId());
  }

  public static List<EmailTemplateDTO> toDTO(List<EmailTemplate> emailTemplates) {
    return emailTemplates == null ? Collections.emptyList()
                                  : emailTemplates.stream().map(EntityMapper::toDTO).collect(Collectors.toList());
  }

}
