/*
 * 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 static io.meeds.deeds.constant.CommonConstants.DEED_EMAIL_CODE_CONFIRMATION_TEMPLATE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

import javax.mail.internet.MimeMessage;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.domain.Pageable;

import org.exoplatform.commons.ObjectAlreadyExistsException;
import org.exoplatform.services.mail.MailService;
import org.exoplatform.wiki.model.Page;
import org.exoplatform.wiki.service.NoteService;

import io.meeds.deeds.constant.ObjectNotFoundException;
import io.meeds.deeds.listerner.model.EmailSendingCommand;
import io.meeds.tenant.provisioning.model.AutomaticStatus;
import io.meeds.tenant.provisioning.model.EmailTemplate;
import io.meeds.tenant.provisioning.storage.EmailTemplateRepository;

@SpringBootTest(classes = {
    DeedEmailService.class,
})
class DeedEmailServiceTest {

  @Mock
  private NoteService                       noteService;

  @Mock
  private MailService                       mailService;

  @MockBean
  private EmailTemplateRepository           emailTemplateRepository;

  @MockBean
  private TenantProvisioningSettingsService tenantProvisioningSettingsService;;

  @MockBean
  private TenantProvisioningService         tenantProvisioningService;

  @Autowired
  private DeedEmailService                  deedEmailService;

  @BeforeEach
  void setup() {
    deedEmailService.mailService = mailService;
    deedEmailService.noteService = noteService;
  }

  @Test
  void testGetEmailTemplates() {
    @SuppressWarnings("unchecked")
    org.springframework.data.domain.Page<EmailTemplate> page = mock(org.springframework.data.domain.Page.class);
    when(emailTemplateRepository.findAll(any(Pageable.class))).thenReturn(page);
    when(page.getContent()).thenReturn(Collections.emptyList());
    List<EmailTemplate> emailTemplates = deedEmailService.getEmailTemplates();
    assertNotNull(emailTemplates);
    assertTrue(emailTemplates.isEmpty());
  }

  @Test
  void testCreateTemplate() throws ObjectAlreadyExistsException {
    when(emailTemplateRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0, EmailTemplate.class));
    EmailTemplate emailTemplate = new EmailTemplate();
    emailTemplate.setAutomaticStatus(AutomaticStatus.START_CONFIRMED);

    EmailTemplate savedEmailTemplate = deedEmailService.createTemplate(emailTemplate);
    assertEquals(emailTemplate, savedEmailTemplate);

    emailTemplate.setId("@id");
    assertThrows(ObjectAlreadyExistsException.class, () -> deedEmailService.createTemplate(emailTemplate));
  }

  @Test
  void testUpdateTemplate() throws ObjectNotFoundException {
    when(emailTemplateRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0, EmailTemplate.class));
    EmailTemplate emailTemplate = new EmailTemplate();
    emailTemplate.setId("@id");
    assertThrows(ObjectNotFoundException.class, () -> deedEmailService.updateTemplate(emailTemplate));

    when(emailTemplateRepository.findById(emailTemplate.getId())).thenReturn(Optional.of(emailTemplate));
    EmailTemplate savedEmailTemplate = deedEmailService.updateTemplate(emailTemplate);
    assertEquals(emailTemplate, savedEmailTemplate);
  }

  @Test
  void testDeleteTemplate() throws ObjectNotFoundException {
    when(emailTemplateRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0, EmailTemplate.class));
    EmailTemplate emailTemplate = new EmailTemplate();
    emailTemplate.setId("@id");
    assertThrows(ObjectNotFoundException.class, () -> deedEmailService.deleteTemplate(emailTemplate.getId()));

    when(emailTemplateRepository.findById(emailTemplate.getId())).thenReturn(Optional.of(emailTemplate));
    deedEmailService.deleteTemplate(emailTemplate.getId());
    verify(emailTemplateRepository, times(1)).deleteById(emailTemplate.getId());
  }

  @Test
  void testSendEmail() throws Exception {
    long nftId = 1l;
    long notePageId = 3l;
    AutomaticStatus automaticStatus = AutomaticStatus.START_CONFIRMED;

    when(emailTemplateRepository.findByAutomaticStatus(any())).thenReturn(Optional.empty());
    assertThrows(ObjectNotFoundException.class, () -> deedEmailService.sendEmail(nftId, automaticStatus));

    reset(emailTemplateRepository);
    EmailTemplate emailTemplate = new EmailTemplate();
    emailTemplate.setId("@id");
    emailTemplate.setAutomaticStatus(automaticStatus);

    when(emailTemplateRepository.findByAutomaticStatus(any())).thenReturn(Optional.of(emailTemplate));
    deedEmailService.sendEmail(nftId, automaticStatus);
    verify(mailService, never()).sendMessageInFuture(any(MimeMessage.class));
    verify(mailService, never()).sendMessage(any(MimeMessage.class));

    emailTemplate.setNotePageId(notePageId);
    assertThrows(ObjectNotFoundException.class, () -> deedEmailService.sendEmail(nftId, automaticStatus));

    Page notePage = mock(Page.class);
    String noteContent = "Mail Body";
    when(noteService.getNoteById(String.valueOf(notePageId))).thenReturn(notePage);
    when(notePage.getContent()).thenReturn(noteContent);

    String emailSubject = "Mail Subject";
    emailTemplate.setMailSubject(emailSubject);
    deedEmailService.sendEmail(nftId, automaticStatus);
    verify(mailService, never()).sendMessageInFuture(any(MimeMessage.class));
    verify(mailService, never()).sendMessage(any(MimeMessage.class));

    String managerEmail = "manager@test.com";
    when(tenantProvisioningService.getManagerEmail(nftId)).thenReturn(managerEmail);
    String emailSender = "Sender Full Email<mail@test.com>";
    String[] emailBcc = {
        "Test User<mail@test.com>"
    };
    String emailSignature = "Congrats!";
    when(tenantProvisioningSettingsService.getSenderFullEmail()).thenReturn(emailSender);
    when(tenantProvisioningSettingsService.getEmailBcc()).thenReturn(emailBcc);
    when(tenantProvisioningSettingsService.getEmailSignature()).thenReturn(emailSignature);
    when(tenantProvisioningSettingsService.getEmailLogoData()).thenReturn(new byte[0]);

    deedEmailService.sendEmail(nftId, automaticStatus);
    verify(mailService, times(1)).sendMessageInFuture(any(MimeMessage.class));
    verify(mailService, never()).sendMessage(any(MimeMessage.class));

    when(tenantProvisioningSettingsService.getEmailLogoData()).thenReturn(new byte[] {
        (byte) 1, (byte) 2, (byte) 3
    });

    deedEmailService.sendEmail(nftId, emailSubject, noteContent, false);
    verify(mailService, times(1)).sendMessage(argThat(new ArgumentMatcher<MimeMessage>() {
      @Override
      public boolean matches(MimeMessage mimeMessage) {
        try {
          assertNotNull(mimeMessage);
          assertNotNull(mimeMessage.getFrom());
          assertNotNull(mimeMessage.getFrom());
          assertEquals(1, mimeMessage.getFrom().length);
          assertEquals(emailSubject, mimeMessage.getSubject());
          return true;
        } catch (Exception e) {
          fail(e);
          return false;
        }
      }
    }));
  }

  @Test
  void testSendEmailCommand() throws Exception {
    long notePageId = 3l;
    String to = "manager@test.com";
    AutomaticStatus automaticStatus = AutomaticStatus.EMAIL_CONFIRMATION_CODE;
    String paramName = "emailParam";
    String paramValue = "PARAM_VALUE";

    EmailSendingCommand emailSendingCommand = new EmailSendingCommand(to,
                                                                      "fake_template",
                                                                      Collections.singletonMap(paramName, paramValue));
    assertThrows(IllegalArgumentException.class, () -> deedEmailService.sendEmail(emailSendingCommand));

    assertEquals(DEED_EMAIL_CODE_CONFIRMATION_TEMPLATE, automaticStatus.name());
    emailSendingCommand.setTemplate(DEED_EMAIL_CODE_CONFIRMATION_TEMPLATE);
    assertThrows(ObjectNotFoundException.class, () -> deedEmailService.sendEmail(emailSendingCommand));

    when(emailTemplateRepository.findByAutomaticStatus(automaticStatus)).thenReturn(Optional.empty());
    assertThrows(ObjectNotFoundException.class, () -> deedEmailService.sendEmail(emailSendingCommand));

    reset(emailTemplateRepository);
    EmailTemplate emailTemplate = new EmailTemplate();
    emailTemplate.setId("@id");
    emailTemplate.setAutomaticStatus(automaticStatus);

    when(emailTemplateRepository.findByAutomaticStatus(any())).thenReturn(Optional.of(emailTemplate));
    assertThrows(ObjectNotFoundException.class, () -> deedEmailService.sendEmail(emailSendingCommand));

    verify(mailService, never()).sendMessageInFuture(any(MimeMessage.class));
    verify(mailService, never()).sendMessage(any(MimeMessage.class));

    emailTemplate.setNotePageId(notePageId);

    Page notePage = mock(Page.class);
    String noteContent = "Mail Body ## " + paramName + " ##-## " + paramName + " ##";
    when(noteService.getNoteById(String.valueOf(notePageId))).thenReturn(notePage);
    when(notePage.getContent()).thenReturn(noteContent);

    String emailSubject = "Mail Subject ## " + paramName + " ##-## " + paramName + " ##";
    emailTemplate.setMailSubject(emailSubject);
    String emailSender = "Sender Full Email<mail@test.com>";
    String[] emailBcc = {
        "Test User<mail@test.com>"
    };
    String emailSignature = "Congrats!";
    when(tenantProvisioningSettingsService.getSenderFullEmail()).thenReturn(emailSender);
    when(tenantProvisioningSettingsService.getEmailBcc()).thenReturn(emailBcc);
    when(tenantProvisioningSettingsService.getEmailSignature()).thenReturn(emailSignature);
    when(tenantProvisioningSettingsService.getEmailLogoData()).thenReturn(new byte[0]);

    deedEmailService.sendEmail(emailSendingCommand);
    verify(mailService, times(1)).sendMessageInFuture(argThat(new ArgumentMatcher<MimeMessage>() {
      @Override
      public boolean matches(MimeMessage mimeMessage) {
        try {
          assertNotNull(mimeMessage);
          assertNotNull(mimeMessage.getFrom());
          assertNotNull(mimeMessage.getFrom());
          assertEquals(1, mimeMessage.getFrom().length);
          assertEquals(emailSubject.replace("## " + paramName + " ##", paramValue), mimeMessage.getSubject());
          assertNotEquals(mimeMessage.getSubject().indexOf(paramValue), mimeMessage.getSubject().lastIndexOf(paramValue));
          return true;
        } catch (Exception e) {
          fail(e);
          return false;
        }
      }
    }));
  }

}
