/**
 * Copyright (c) 2022-2024 Linagora
 * 
 * This program/library 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 2.1 of the License, or (at your
 * option) any later version.
 * 
 * This program/library 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/library; If not, see http://www.gnu.org/licenses/
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.bc.quartz;

import static com.jayway.awaitility.Awaitility.await;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeoutException;
import java.util.logging.LogRecord;

import javax.jbi.messaging.ExchangeStatus;

import org.junit.Before;
import org.junit.Test;
import org.ow2.petals.commons.log.FlowLogData;
import org.ow2.petals.commons.log.Level;
import org.ow2.petals.component.framework.junit.RequestMessage;
import org.ow2.petals.component.framework.junit.StatusMessage;
import org.ow2.petals.component.framework.junit.impl.message.StatusToConsumerMessage;
import org.ow2.petals.component.framework.listener.AbstractListener;

import com.jayway.awaitility.Duration;

/**
 * Unit test of the BC Quartz
 * 
 * @author Christophe DENEUX - Linagora
 *
 */
public class QuartzBcWithPlaceholderTest extends AbstractComponentWithPlaceholdersTest {

    private static final String PLACEHOLDER_CRON_EXPR = "placeholder.cron-expression";

    private static final String CRON_EXPR_PATTERN = "%1$d * * * * ?";

    private static final long INITIAL_SPECIFIC_SECOND = 10;

    private static final long NEW_SPECIFIC_SECOND = 30;

    @Before
    public void initPlaceholders() throws IOException {
        final Properties componentProperties = new Properties();
        componentProperties.setProperty(PLACEHOLDER_CRON_EXPR,
                String.format(CRON_EXPR_PATTERN, INITIAL_SPECIFIC_SECOND));
        try (final FileOutputStream fos = new FileOutputStream(COMPONENT_PROPERTIES_FILE)) {
            componentProperties.store(fos, "Placeholders initialized");
        }
        COMPONENT_UNDER_TEST.getComponentObject().reloadPlaceHolders();
    }

    /**
     * <p>
     * Check the deployment and processing of a valid SU using a placeholder to define the CRON expression.
     * </p>
     * <p>
     * Expected results:
     * </p>
     * <ul>
     * <li>the SU starts correctly,</li>
     * <li>a service invocation is correctly done automatically in the right time using the CRON expression as
     * placeholder, with the correct MONIT traces</li>
     * <li>a service invocation is correctly done automatically in the right time after CRON expression placeholder
     * reload</li>
     * <li>the SU is undeployed correctly,</li>
     * </ul>
     */
    @Test
    public void reloadCronExpr() throws Exception {

        assertTrue(COMPONENT_UNDER_TEST.isInstalled());
        assertTrue(COMPONENT_UNDER_TEST.isStarted());

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME,
                createHelloConsumes(true, true, "${" + PLACEHOLDER_CRON_EXPR + "}"));
        assertTrue(COMPONENT_UNDER_TEST.isServiceDeployed(SU_CONSUMER_NAME));

        // Wait that the first triggering execution passed
        assertTriggerExecution(INITIAL_SPECIFIC_SECOND, false);

        // Change placeholder configuration and reload it.
        final Properties componentProperties = new Properties();
        componentProperties.setProperty(PLACEHOLDER_CRON_EXPR, String.format(CRON_EXPR_PATTERN, NEW_SPECIFIC_SECOND));
        try (final FileOutputStream fos = new FileOutputStream(COMPONENT_PROPERTIES_FILE)) {
            componentProperties.store(fos, "Updated placeholders");
        }
        COMPONENT_UNDER_TEST.getComponentObject().reloadPlaceHolders();

        // Wait that a new triggering execution passed
        assertTriggerExecution(NEW_SPECIFIC_SECOND, false);

        // Undeploy service unit
        COMPONENT_UNDER_TEST.undeployService(SU_CONSUMER_NAME);
        assertFalse(COMPONENT_UNDER_TEST.isServiceDeployed(SU_CONSUMER_NAME));
    }

    /**
     * <p>
     * Check the deployment and processing of a valid SU.
     * </p>
     * <p>
     * Expected results:
     * </p>
     * <ul>
     * <li>no error occurs,</li>
     * <li>the expected service provider is invoked,</li>
     * <li>a timeout occurs waiting the status message of the service provider,</li>
     * <li>a timeout warning message is logged,</li>
     * <li>expected MONIT traces are logged</li>
     * </ul>
     */
    @Test
    public void timeout() throws Exception {

        assertTrue(COMPONENT_UNDER_TEST.isInstalled());
        assertTrue(COMPONENT_UNDER_TEST.isStarted());

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME,
                createHelloConsumes(true, true, "${" + PLACEHOLDER_CRON_EXPR + "}"));
        assertTrue(COMPONENT_UNDER_TEST.isServiceDeployed(SU_CONSUMER_NAME));

        // Wait that the first triggering execution passed
        // Note: Log records are cleared on the unit test startup
        assertTriggerExecution(INITIAL_SPECIFIC_SECOND, true);

        // Undeploy service unit
        COMPONENT_UNDER_TEST.undeployService(SU_CONSUMER_NAME);
        assertFalse(COMPONENT_UNDER_TEST.isServiceDeployed(SU_CONSUMER_NAME));
    }

    private void assertTriggerExecution(final long specificSecond, final boolean enableTimeout)
            throws TimeoutException, InterruptedException {

        // Wait that the trigger fired the MONIT traces
        IN_MEMORY_LOG_HANDLER.clear();
        await().atMost(Duration.ONE_MINUTE).pollInterval(Duration.ONE_SECOND).until(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                final List<LogRecord> logs = IN_MEMORY_LOG_HANDLER.getAllRecords(Level.MONIT);
                if (logs.size() == 1 && logs.get(0).getParameters().length == 1
                        && logs.get(0).getParameters()[0] instanceof QuartzConsumeExtFlowStepBeginLogData) {
                    return true;
                } else {
                    return false;
                }
            }
        });

        final RequestMessage message = COMPONENT_UNDER_TEST.pollRequestFromConsumer();
        final StatusMessage response = new StatusToConsumerMessage(message, ExchangeStatus.DONE);
        if (enableTimeout) {
            Thread.sleep((long) (HELLO_TIMEOUT * 1.5));
        }
        COMPONENT_UNDER_TEST.pushStatusToConsumer(response, true);

        // Assert MONIT traces
        final List<LogRecord> monitLogs = IN_MEMORY_LOG_HANDLER.getAllRecords(Level.MONIT);
        assertEquals(4, monitLogs.size());
        final FlowLogData consumeBeginExtFlowLogData = assertMonitConsumerExtBeginLog(monitLogs.get(0));
        assertNotNull(consumeBeginExtFlowLogData.get(QuartzConsumeExtFlowStepBeginLogData.EVENTTIME_KEY));
        final FlowLogData providerBeginExtFlowLogData = assertMonitProviderBeginLog(HELLO_INTERFACE, HELLO_SERVICE,
                HELLO_ENDPOINT_NAME, SAY_HELLO_OPERATION, monitLogs.get(1));
        if (enableTimeout) {
            assertMonitConsumerExtTimeoutLog(HELLO_TIMEOUT, HELLO_INTERFACE, HELLO_SERVICE, HELLO_ENDPOINT_NAME,
                    SAY_HELLO_OPERATION, consumeBeginExtFlowLogData, monitLogs.get(2));
            assertMonitProviderEndLog(providerBeginExtFlowLogData, monitLogs.get(3));
        } else {
            assertMonitProviderEndLog(providerBeginExtFlowLogData, monitLogs.get(2));
            assertMonitConsumerExtEndLog(consumeBeginExtFlowLogData, monitLogs.get(3));
        }

        final Calendar triggerExecDate = Calendar.getInstance();
        triggerExecDate.setTimeInMillis(monitLogs.get(0).getMillis());
        assertEquals(specificSecond, triggerExecDate.get(Calendar.SECOND));

        if (enableTimeout) {
            // Assertion about the timeout warning message
            final List<LogRecord> warnRecords = IN_MEMORY_LOG_HANDLER.getAllRecords(java.util.logging.Level.WARNING);
            assertEquals(1, warnRecords.size());
            assertEquals(
                    String.format(AbstractListener.TIMEOUT_WARN_LOG_MSG_PATTERN, HELLO_TIMEOUT,
                            HELLO_INTERFACE.toString(), HELLO_SERVICE.toString(), HELLO_ENDPOINT_NAME,
                            SAY_HELLO_OPERATION.toString(),
                            consumeBeginExtFlowLogData.get(FlowLogData.FLOW_INSTANCE_ID_PROPERTY_NAME),
                            consumeBeginExtFlowLogData.get(FlowLogData.FLOW_STEP_ID_PROPERTY_NAME)),
                    warnRecords.get(0).getMessage());
        }
    }
}
