/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.channel.cc.orchestrator.ui.ws.rs.operation;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

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

import javax.annotation.Resource;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.core.Response.Status;

import org.jboss.arquillian.extension.rest.client.ArquillianResteasyResource;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.junit.Test;
import org.junit.runner.RunWith;

import com.scytl.products.ov.channel.cc.orchestrator.mixdec.domain.model.BallotBoxStatus;
import com.scytl.products.ov.channel.cc.orchestrator.mixdec.domain.model.MixDecBallotBoxStatus;
import com.scytl.products.ov.channel.cc.orchestrator.ui.ws.rs.config.MockMixingNodeListener;
import com.scytl.products.ov.commons.dto.mixing.MixingPayloadMockBuilder;
import com.scytl.products.ov.commons.errormanagement.ExecutionPolicy;
import com.scytl.products.ov.commons.errormanagement.ExponentialBackoffExecutionPolicy;
import com.scytl.products.ov.commons.messaging.MessageListener;
import com.scytl.products.ov.commons.messaging.MessagingService;

/**
 * WARNING: This test can only be run as part of the MessageBrokerTestSuite.
 */
@RunWith(Arquillian.class)
public class MixDecBallotBoxesResourceArquillian extends MixDecArquillianTestBase {

    @PersistenceContext(unitName = "persistenceUnitJdbc")
    private EntityManager entityManager;

    @Resource
    private UserTransaction userTransaction;

    @Inject
    private MessagingService messagingService;

    /**
     * Runs a test based on the belief that nothing will fail.
     */
    @Test
    public void testMixingDecryptionHappyPath(@ArquillianResteasyResource("") ResteasyWebTarget webTarget)
            throws Throwable {
        assertThat(getBallotBoxStatus(webTarget, BALLOT_BOX_ID), is(BallotBoxStatus.STATUS_NOT_FOUND));

        // Start the mock mixing control component nodes
        startUpMixingNodes();

        // Start processing the ballot box.
        List<BallotBoxStatus> ballotBoxStatuses = startMixing(webTarget, BALLOT_BOX_ID);
        ballotBoxStatuses.forEach(bbStatus -> {
            if (bbStatus.getBallotBoxId().equals(BALLOT_BOX_ID)) {
                assertThat(bbStatus.getProcessStatus(), is(BallotBoxStatus.STATUS_CLOSED));
            }
        });

        // Check the ballot box status
        assertThat(getBallotBoxStatus(webTarget, BALLOT_BOX_ID), is(BallotBoxStatus.STATUS_CLOSED));

        // The ballot box should eventually be closed. It will wait for 31
        // seconds
        // (1+2+4+8+16).
        ExecutionPolicy policy =
            new ExponentialBackoffExecutionPolicy(3, 1000, Collections.singleton(ProcessingException.class));
        policy.execute(() -> {
            assertThat(downloadMixingResults(webTarget, BALLOT_BOX_ID).getStatus(), is(Status.OK.getStatusCode()));
            return null;
        });

        // Check the ballot box status
        assertThat(getBallotBoxStatus(webTarget, BALLOT_BOX_ID), is(BallotBoxStatus.STATUS_MIXED));
    }

    @Test
    public void doNotMixClosedBallotBox(@ArquillianResteasyResource("") ResteasyWebTarget webTarget)
            throws SystemException {
        // A closed ballot box. Can't be mixed.
        populateBallotBoxStatus(BallotBoxStatus.STATUS_CLOSED);

        // Check the ballot box status
        assertThat(getBallotBoxStatus(webTarget, BALLOT_BOX_ID), is(BallotBoxStatus.STATUS_CLOSED));

        // Start processing the ballot boxes.
        List<BallotBoxStatus> ballotBoxStatuses = startMixing(webTarget, BALLOT_BOX_ID);

        // Check the ballot box status
        ballotBoxStatuses
            .forEach(bbStatus -> assertThat(bbStatus.getProcessStatus(), is(BallotBoxStatus.STATUS_ERROR)));

        // The ballot box should be left in error.
        assertThat(getBallotBoxStatus(webTarget, BALLOT_BOX_ID), is(BallotBoxStatus.STATUS_CLOSED));
    }

    @Test
    public void mixErrorBallotBox(@ArquillianResteasyResource("") ResteasyWebTarget webTarget) throws Throwable {
        // Prepare ballot box statuses in database
        populateBallotBoxStatus(BallotBoxStatus.STATUS_ERROR);

        // Check the ballot box status
        assertThat(getBallotBoxStatus(webTarget, BALLOT_BOX_ID), is(BallotBoxStatus.STATUS_ERROR));

        // Start processing the ballot boxes.
        List<BallotBoxStatus> ballotBoxStatuses = startMixing(webTarget, BALLOT_BOX_ID);

        // The ballot box is left in error.
        ballotBoxStatuses
            .forEach(bbStatus -> assertThat(bbStatus.getProcessStatus(), is(BallotBoxStatus.STATUS_CLOSED)));

        // The ballot box should eventually be closed. It will wait for 31
        // seconds
        // (1+2+4+8+16).
        ExecutionPolicy policy =
            new ExponentialBackoffExecutionPolicy(3, 1000, Collections.singleton(ProcessingException.class));
        policy.execute(() -> {
            assertThat(downloadMixingResults(webTarget, BALLOT_BOX_ID).getStatus(), is(Status.OK.getStatusCode()));
            return null;
        });

        // Check the ballot box status
        assertThat(getBallotBoxStatus(webTarget, BALLOT_BOX_ID), is(BallotBoxStatus.STATUS_MIXED));
    }

    /**
     * Store a status with a particular ballot box.
     *
     * @param status
     *            the status to store
     */
    private void populateBallotBoxStatus(String status) throws SystemException {
        try {
            userTransaction.begin();

            entityManager.createQuery("DELETE FROM MixDecBallotBoxStatus").executeUpdate();

            MixDecBallotBoxStatus entity = new MixDecBallotBoxStatus();
            entity.setBallotBoxId(BALLOT_BOX_ID);
            entity.setElectionEventId(MixingPayloadMockBuilder.ELECTION_EVENT_ID);
            entity.setTenantId(MixingPayloadMockBuilder.TENANT_ID);
            entity.setStatus(status);
            entityManager.merge(entity);

            userTransaction.commit();
        } catch (NotSupportedException | SystemException | HeuristicMixedException | HeuristicRollbackException
                | RollbackException e) {
            userTransaction.rollback();
        }
    }

    @Override
    protected MessageListener createListener(String nodeName, String requestQueueName, String responseQueueName) {
        return new MockMixingNodeListener(messagingService, nodeName, requestQueueName, responseQueueName);
    }
}
