/**
 * Copyright (c) 2016-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.util.List;
import java.util.concurrent.Callable;
import java.util.logging.LogRecord;

import javax.jbi.JBIException;
import javax.jbi.management.DeploymentException;
import javax.jbi.messaging.ExchangeStatus;

import org.junit.Test;
import org.ow2.petals.commons.log.FlowLogData;
import org.ow2.petals.commons.log.Level;
import org.ow2.petals.component.framework.AbstractComponent;
import org.ow2.petals.component.framework.junit.RequestMessage;
import org.ow2.petals.component.framework.junit.StatusMessage;
import org.ow2.petals.component.framework.junit.impl.ConsumesServiceConfiguration;
import org.ow2.petals.component.framework.junit.impl.message.StatusToConsumerMessage;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;

import com.ebmwebsourcing.easycommons.lang.UncheckedException;
import com.ebmwebsourcing.easycommons.lang.reflect.ReflectionHelper;
import com.jayway.awaitility.Duration;

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

    /**
     * <p>
     * Check the component start-up under error condition (an error is detected during startup).
     * </p>
     * <p>
     * Expected results: the component start-up fails.
     * </p>
     */
    @Test
    public void componentStartUnderError() throws SchedulerException {

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

        COMPONENT_UNDER_TEST.stop();

        // To generate an error, we force to shutdown the embedded Quartz scheduler. So it can'nt be re-started.
        final AbstractComponent component = COMPONENT_UNDER_TEST.getComponentObject();
        assertTrue(component instanceof QuartzBc);
        final QuartzBc quartzSe = (QuartzBc) component;
        final Scheduler quartzScheduler = (Scheduler) ReflectionHelper.getFieldValue(QuartzBc.class, quartzSe,
                "quartzScheduler", false);
        assertNotNull(quartzScheduler);
        quartzScheduler.shutdown();

        try {
            COMPONENT_UNDER_TEST.start();
            fail("JBIException not thrown");
        } catch (final UncheckedException e) {
            assertTrue(e.getCause() instanceof JBIException);

        }

    }

    @Test
    public void lifecycles() throws Exception {

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

        COMPONENT_UNDER_TEST.stop();
        assertFalse(COMPONENT_UNDER_TEST.isStarted());

        COMPONENT_UNDER_TEST.start();
        assertTrue(COMPONENT_UNDER_TEST.isStarted());

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, createHelloConsumes(true, true, "0/15 * * * * ?"));
        assertTrue(COMPONENT_UNDER_TEST.isServiceDeployed(SU_CONSUMER_NAME));

        COMPONENT_UNDER_TEST.stop();
        assertFalse(COMPONENT_UNDER_TEST.isStarted());

        COMPONENT_UNDER_TEST.undeployService(SU_CONSUMER_NAME);
        assertFalse(COMPONENT_UNDER_TEST.isServiceDeployed(SU_CONSUMER_NAME));

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, createHelloConsumes(true, true, "0/15 * * * * ?"));
        assertTrue(COMPONENT_UNDER_TEST.isServiceDeployed(SU_CONSUMER_NAME));

        COMPONENT_UNDER_TEST.start();
        assertTrue(COMPONENT_UNDER_TEST.isStarted());
    }

    /**
     * <p>
     * Check the deployment and processing of a valid SU.
     * </p>
     * <p>
     * Expected results:
     * </p>
     * <ul>
     * <li>the SU starts correctly,</li>
     * <li>a service invocation is correctly done automatically, with the correct MONIT traces</li>
     * <li>the SU is undeployed correctly,</li>
     * </ul>
     */
    @Test
    public void suStartAndStop() throws Exception {

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

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, createHelloConsumes(true, true, "0/15 * * * * ?"));
        assertTrue(COMPONENT_UNDER_TEST.isServiceDeployed(SU_CONSUMER_NAME));

        // Wait that the trigger fired the MONIT traces
        IN_MEMORY_LOG_HANDLER.clear();
        await().atMost(Duration.ONE_MINUTE).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);
        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));
        assertMonitProviderEndLog(providerBeginExtFlowLogData, monitLogs.get(2));
        assertMonitConsumerExtEndLog(consumeBeginExtFlowLogData, monitLogs.get(3));

        COMPONENT_UNDER_TEST.undeployService(SU_CONSUMER_NAME);
        assertFalse(COMPONENT_UNDER_TEST.isServiceDeployed(SU_CONSUMER_NAME));
    }

    /**
     * Check that a SU start fails if no CRON expression is defined in SU
     */
    @Test(expected = DeploymentException.class)
    public void suCronExpressionNotSet() throws Exception {

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, createHelloConsumes(true, true, null));
    }

    /**
     * Check that a SU start fails if an empty CRON expression is set in SU
     */
    @Test(expected = DeploymentException.class)
    public void suCronExpressionEmpty() throws Exception {

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, createHelloConsumes(true, true, ""));
    }

    /**
     * Check that a SU start fails on an invalid CRON expression
     */
    @Test(expected = DeploymentException.class)
    public void suCronExpressionInvalid() throws Exception {

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, createHelloConsumes(true, true, "incorrect cron exp"));
    }

    /**
     * Check that a SU containing two consumes of the same service-endpoint (endpoint-name is given) with the no
     * operation fails to start
     */
    @Test(expected = DeploymentException.class)
    public void suWith2ConsumesEdpFixedNoOp() throws Exception {

        final ConsumesServiceConfiguration firstServiceConfiguration = createHelloConsumes(true, true, false,
                "0/15 * * * * ?");
        final ConsumesServiceConfiguration secondServiceConfiguration = createHelloConsumes(true, true, false,
                "0/15 * * * * ?");
        firstServiceConfiguration.addServiceConfigurationDependency(secondServiceConfiguration);

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, firstServiceConfiguration);
    }

    /**
     * Check that a SU containing two consumes of the same service-endpoint (endpoint is autogenerated) with the no
     * operation fails to start
     */
    @Test(expected = DeploymentException.class)
    public void suWith2ConsumesEdpAutogeneratedNoOp() throws Exception {

        final ConsumesServiceConfiguration firstServiceConfiguration = createHelloConsumes(true, false, false,
                "0/15 * * * * ?");
        final ConsumesServiceConfiguration secondServiceConfiguration = createHelloConsumes(true, false, false,
                "0/15 * * * * ?");
        firstServiceConfiguration.addServiceConfigurationDependency(secondServiceConfiguration);

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, firstServiceConfiguration);
    }

    /**
     * Check that a SU containing two consumes of the same service-endpoint (endpoint-name is given) with the same
     * operations fails to start
     */
    @Test(expected = DeploymentException.class)
    public void suWith2ConsumesEdpFixedSameOp() throws Exception {

        final ConsumesServiceConfiguration firstServiceConfiguration = createHelloConsumes(true, true,
                "0/15 * * * * ?");
        final ConsumesServiceConfiguration secondServiceConfiguration = createHelloConsumes(true, true,
                "0/15 * * * * ?");
        firstServiceConfiguration.addServiceConfigurationDependency(secondServiceConfiguration);

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, firstServiceConfiguration);
    }

    /**
     * Check that a SU containing two consumes of the same service-endpoint (endpoint is autogenerated) with the same
     * operations fails to start
     */
    @Test(expected = DeploymentException.class)
    public void suWith2ConsumesEdpAutogeneratedSameOp() throws Exception {

        final ConsumesServiceConfiguration firstServiceConfiguration = createHelloConsumes(true, false,
                "0/15 * * * * ?");
        final ConsumesServiceConfiguration secondServiceConfiguration = createHelloConsumes(true, false,
                "0/15 * * * * ?");
        firstServiceConfiguration.addServiceConfigurationDependency(secondServiceConfiguration);

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, firstServiceConfiguration);
    }

    /**
     * Check that a SU containing two consumes of the same service-endpoint (endpoint-name is given) but different
     * operations starts correctly
     */
    @Test
    public void suWith2ConsumesEdpFixedDifferentOp() throws Exception {

        final ConsumesServiceConfiguration firstServiceConfiguration = createHelloConsumes(true, true,
                "0/15 * * * * ?");
        final ConsumesServiceConfiguration secondServiceConfiguration = createHelloConsumes(true, true,
                "0/15 * * * * ?");
        secondServiceConfiguration.setOperation(PRINT_HELLO_OPERATION);
        firstServiceConfiguration.addServiceConfigurationDependency(secondServiceConfiguration);

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, firstServiceConfiguration);
    }

    /**
     * Check that a SU containing two consumes of the same service-endpoint (endpoint autogenerated) but different
     * operations starts correctly
     */
    @Test
    public void suWith2ConsumesEdpAutogeneratedDifferentOp() throws Exception {

        final ConsumesServiceConfiguration firstServiceConfiguration = createHelloConsumes(true, false,
                "0/15 * * * * ?");
        final ConsumesServiceConfiguration secondServiceConfiguration = createHelloConsumes(true, false,
                "0/15 * * * * ?");
        secondServiceConfiguration.setOperation(PRINT_HELLO_OPERATION);
        firstServiceConfiguration.addServiceConfigurationDependency(secondServiceConfiguration);

        COMPONENT_UNDER_TEST.deployService(SU_CONSUMER_NAME, firstServiceConfiguration);
    }
}
