package com.atlassian.stash.internal.mail;

import com.atlassian.bitbucket.DataGenerationUtils;
import com.atlassian.bitbucket.Product;
import com.atlassian.bitbucket.i18n.I18nKey;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.bitbucket.mail.HtmlCssInliner;
import com.atlassian.bitbucket.mail.MailMessage;
import com.atlassian.bitbucket.mail.SoyMailMessageRequest;
import com.atlassian.bitbucket.timezone.UserTimeZoneSupplier;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.DummySecurityService;
import com.atlassian.bitbucket.user.SecurityService;
import com.atlassian.soy.renderer.SoyException;
import com.atlassian.soy.renderer.SoyTemplateRenderer;
import com.atlassian.stash.internal.user.InternalLocaleManager;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.Writer;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;

import static com.google.common.collect.ImmutableList.of;
import static org.hamcrest.collection.IsMapContaining.hasEntry;
import static org.junit.Assert.*;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class SoyMailBuilderImplTest {

    private static final I18nKey TEST_SUBJECT_KEY = new I18nKey("test.subject.key");
    private static final String TEST_SOY_MODULE = "MySoyTemplateModuleName";
    private static final String TEST_SOY_TEMPLATE = "MySoyTemplateName";
    private static final String TEST_CSS_MODULE = "MyCssModuleName";
    private static final String EMAIL1 = DataGenerationUtils.MAX_EMAIL_ADDRESS;
    private static final String EMAIL2 = "someother@email.com";
    private static final String TEST_HEADER_VALUE = "Value";
    private static final String TEST_HEADER_NAME = "Test-Header";
    @Mock
    private SoyTemplateRenderer soyTemplateRenderer;
    @Mock
    private HtmlCssInliner htmlCssInliner;
    @Spy
    private I18nService i18nService = new SimpleI18nService();
    @Spy
    private SecurityService securityService = new DummySecurityService();
    @Mock
    private InternalLocaleManager localeManager;
    @InjectMocks
    private SoyMailBuilderImpl soyBuilder;
    @Captor
    private ArgumentCaptor<Map<String, Object>> injectedDataCaptor;
    @Mock
    private UserTimeZoneSupplier userTimeZoneSupplier;

    private Map<String, Object> context = new HashMap<String, Object>() {
        {
            put("key", "value");
            put("key2", "value2");
        }
    };
    private static final String EXAMPLE_HTML = "HTML string";
    private static final String EXAMPLE_HTML_CSS = "HTML with CSS";
    private static final String EMPTY_MESSAGE = "";


    @Test
    public void testContentAndSubject() throws Exception {
        ApplicationUser user1 = getUser(EMAIL1);
        Iterable<ApplicationUser> recipients = Lists.newArrayList(user1);
        mockRender();

        Iterable<MailMessage> messages = buildMessage(recipients, null);

        verifyRenderer(EXAMPLE_HTML, 1);
        assertEquals(1, Iterables.size(messages));
        for (MailMessage message : messages) {
            assertEquals(TEST_SUBJECT_KEY.getKey(), message.getSubject());
            assertEquals(EXAMPLE_HTML_CSS, message.getText());
            verifyEmail(message, EMAIL1);
        }
    }

    @Test
    public void testShouldUseUserLocaleInHeader() throws Exception {
        ApplicationUser user1 = getUser(EMAIL1, Locale.CANADA_FRENCH, ZoneOffset.UTC);
        Iterable<ApplicationUser> recipients = Lists.newArrayList(user1);

        mockRender();

        Iterable<MailMessage> messages = buildMessage(recipients, null);

        assertEquals(1, Iterables.size(messages));
        MailMessage mailMessage = messages.iterator().next();
        assertEquals(Locale.CANADA_FRENCH.toLanguageTag(), mailMessage.getHeaders().get("Content-Language"));
    }

    @Test
    public void testShouldUseDefaultLocaleWhenNoUserLocaleSet() throws Exception {
        ApplicationUser user1 = getUser(EMAIL1, null, ZoneOffset.UTC);
        Iterable<ApplicationUser> recipients = Lists.newArrayList(user1);

        mockRender();

        Iterable<MailMessage> messages = buildMessage(recipients, null);

        assertEquals(1, Iterables.size(messages));
        MailMessage mailMessage = messages.iterator().next();
        assertNull(mailMessage.getHeaders().get("Content-Language"));
    }

    @Test
    public void testSend1Recipient() throws Exception {
        ApplicationUser user1 = getUser(EMAIL1);
        Iterable<ApplicationUser> recipients = Lists.newArrayList(user1);

        Iterable<MailMessage> messages = buildMessage(recipients, null);

        verifyRenderer(EMPTY_MESSAGE, 1);
        assertEquals(1, Iterables.size(messages));
        for (MailMessage message : messages) {
            verifyEmail(message, EMAIL1);
        }
    }

    @Test
    public void testMessageHeader() throws Exception {
        ApplicationUser user1 = getUser(EMAIL1);
        Iterable<ApplicationUser> recipients = Lists.newArrayList(user1);
        Function<MailMessage.Builder, MailMessage.Builder> effector =
                input -> input.header(TEST_HEADER_NAME, TEST_HEADER_VALUE);
        Iterable<MailMessage> messages = buildMessage(recipients, effector);

        verifyRenderer(EMPTY_MESSAGE, 1);
        assertEquals(1, Iterables.size(messages));
        for (MailMessage message : messages) {
            assertNotNull(message.getHeaders());
            assertNotNull(message.getHeaders().get(TEST_HEADER_NAME));
            assertEquals(TEST_HEADER_VALUE, message.getHeaders().get(TEST_HEADER_NAME));
            verifyEmail(message, EMAIL1);
        }
    }

    @Test
    public void testSend1RecipientNoEmail() throws Exception {
        ApplicationUser user1 = getUser(" ");
        Iterable<ApplicationUser> recipients = Lists.newArrayList(user1);

        Iterable<MailMessage> messages = buildMessage(recipients, null);

        assertEquals(0, Iterables.size(messages));
    }

    @Test
    public void testSend2Recipients() throws Exception {
        ApplicationUser user1 = getUser(EMAIL1);
        ApplicationUser user2 = getUser(EMAIL2);
        Iterable<ApplicationUser> recipients = Lists.newArrayList(user1, user2);

        Iterable<MailMessage> messages = buildMessage(recipients, null);

        verifyRenderer(EMPTY_MESSAGE, 1);
        assertEquals(2, Iterables.size(messages));
        for (MailMessage message : messages) {
            assertEquals(1, message.getTo().size());
            assertTrue(
                    (message.getTo().contains(EMAIL1) && !message.getTo().contains(EMAIL2))
                            ||
                            (message.getTo().contains(EMAIL2) && !message.getTo().contains(EMAIL1))
            );
        }
    }

    @Test
    public void testSend2Recipients1Email() throws Exception {
        ApplicationUser user1 = getUser(EMAIL1);
        ApplicationUser user2 = getUser(EMAIL1);
        Iterable<ApplicationUser> recipients = Lists.newArrayList(user1, user2);

        Iterable<MailMessage> messages = buildMessage(recipients, null);

        verifyRenderer(EMPTY_MESSAGE, 1);
        assertEquals(1, Iterables.size(messages));
        for (MailMessage message : messages) {
            verifyEmail(message, EMAIL1);
        }
    }

    @Test
    public void testSendCachesOnLocaleAndTimeZone() {
        ApplicationUser user = getUser(EMAIL1, Locale.CANADA, ZoneOffset.UTC);
        ApplicationUser differentTz = getUser(EMAIL1, Locale.CANADA, ZoneOffset.MAX);
        ApplicationUser differentLocale = getUser(EMAIL1, Locale.ENGLISH, ZoneOffset.UTC);
        ApplicationUser cachedUser = getUser(EMAIL1, Locale.CANADA, ZoneOffset.UTC);

        buildMessage(of(user, differentTz, cachedUser, differentLocale), null);

        //Make sure that we only cache one render.
        verifyRenderer(EMPTY_MESSAGE, 3);
    }

    private ApplicationUser getUser(String email) {
        return getUser(email, Locale.ENGLISH, ZoneOffset.UTC);
    }

    private ApplicationUser getUser(String email, Locale locale, ZoneId zoneId) {
        ApplicationUser user = mock(ApplicationUser.class);
        when(user.getEmailAddress()).thenReturn(email);
        when(localeManager.getLocale(eq(user))).thenReturn(locale);
        when(userTimeZoneSupplier.getTimeZone(eq(user))).thenReturn(zoneId);
        return user;
    }

    private void mockRender() throws SoyException {
        doAnswer(invocation -> {
            Writer w = (Writer) invocation.getArguments()[0];
            w.append(EXAMPLE_HTML);
            return null;
        }).when(soyTemplateRenderer).render(any(Writer.class), anyString(), anyString(), eq(context), anyMapOf(String.class, Object.class));
        when(htmlCssInliner.inlineCss(eq(EXAMPLE_HTML), eq(TEST_CSS_MODULE))).thenReturn(EXAMPLE_HTML_CSS);
    }

    private void verifyRenderer(String htmlIn, int noOfRenders) throws SoyException {
        verify(soyTemplateRenderer, times(noOfRenders))
                .render(any(Writer.class), eq(TEST_SOY_MODULE), eq(TEST_SOY_TEMPLATE), eq(context), injectedDataCaptor.capture());
        assertThat(injectedDataCaptor.getValue(), hasEntry("productName", Product.NAME));
        verify(htmlCssInliner, times(noOfRenders)).inlineCss(htmlIn, TEST_CSS_MODULE);
        verifyNoMoreInteractions(soyTemplateRenderer, htmlCssInliner);
    }

    private void verifyEmail(MailMessage message, String email) {
        assertEquals(1, message.getTo().size());
        assertTrue(message.getTo().contains(email));
    }

    private Iterable<MailMessage> buildMessage(Iterable<ApplicationUser> recipients, Function<MailMessage.Builder, MailMessage.Builder> effector) {
        return soyBuilder.build(new SoyMailMessageRequest.Builder()
                .cssModuleKey(TEST_CSS_MODULE)
                .context(context)
                .messageEffector(effector)
                .recipients(recipients)
                .soyTemplateModuleKey(TEST_SOY_MODULE)
                .soyTemplateName(TEST_SOY_TEMPLATE)
                .subjectKey(TEST_SUBJECT_KEY)
                .build());
    }
}
