/*
 * 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.commons.spring;

import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.scytl.products.ov.channel.cc.commons.keymanagement.InvalidKeyStoreException;
import com.scytl.products.ov.channel.cc.commons.keymanagement.InvalidNodeCAException;
import com.scytl.products.ov.channel.cc.commons.keymanagement.InvalidPasswordException;
import com.scytl.products.ov.channel.cc.commons.keymanagement.NodeKeysActivator;
import com.scytl.products.ov.channel.cc.commons.slogger.SecureLoggerManager;
import com.scytl.slogger.SecureLoggerException;

/**
 * Tests of {@link AbstractControlComponentApplication}.
 */
public class AbstractControlComponentApplicationTest {
    private static ExecutorService executor;

    private ConfigurableApplicationContext context;

    private NodeKeysActivator keysActivator;

    private SecureLoggerManager loggerManager;;

    private TestableApplication application;

    @AfterClass
    public static void afterClass() {
        executor.shutdown();
    }

    @BeforeClass
    public static void beforeClass() {
        executor = newSingleThreadExecutor();
    }

    @Before
    public void setUp() {
        context = new AnnotationConfigApplicationContext(
            TestableApplication.class);
        keysActivator = context.getBean(NodeKeysActivator.class);
        loggerManager = context.getBean(SecureLoggerManager.class);
        application = context.getBean(TestableApplication.class);
    }

    @After
    public void tearDown() {
        context.close();
    }

    @Test
    public void testDoRun()
            throws ApplicationException, InterruptedException,
            ExecutionException, InvalidKeyStoreException,
            InvalidPasswordException, InvalidNodeCAException,
            KeyManagementException, NoSuchFileException, IOException,
            SecureLoggerException {
        Future<Void> future = startApplication();
        application.shutdown();
        future.get();
        verify(keysActivator).activateNodeKeys(any(Path.class),
            anyString(), any(Path.class));
        verify(loggerManager).openSecureLogger();
        verify(loggerManager).closeSecureLogger();
        assertTrue(application.isActivated());
        assertTrue(application.isDeactivated());
    }

    @Test(expected = ApplicationException.class)
    public void testDoRunActivateMessageEndpointsError()
            throws ApplicationException, InterruptedException,
            TimeoutException, SecureLoggerException {
        application.setActivateException(new ApplicationException("test"));
        Future<Void> future = startApplication();
        try {
            future.get(1, TimeUnit.SECONDS);
        } catch (ExecutionException e) {
            verify(loggerManager).closeSecureLogger();
            throw (ApplicationException) e.getCause();
        }
    }

    @Test(expected = ApplicationException.class)
    public void testDoRunCloseSecureLoggerError()
            throws SecureLoggerException, InterruptedException,
            ApplicationException, TimeoutException {
        doThrow(new SecureLoggerException("test")).when(loggerManager)
            .closeSecureLogger();
        Future<Void> future = startApplication();
        application.shutdown();
        try {
            future.get(1, TimeUnit.SECONDS);
        } catch (ExecutionException e) {
            throw (ApplicationException) e.getCause();
        }
    }

    @Test(expected = ApplicationException.class)
    public void testDoRunDeactivateMessageEndpointsError()
            throws InterruptedException, TimeoutException,
            SecureLoggerException, ApplicationException {
        application
            .setDeactivateException(new ApplicationException("test"));
        Future<Void> future = startApplication();
        application.shutdown();
        try {
            future.get(1, TimeUnit.SECONDS);
        } catch (ExecutionException e) {
            verify(loggerManager).closeSecureLogger();
            throw (ApplicationException) e.getCause();
        }
    }

    @Test(expected = ApplicationException.class)
    public void testDoRunNodeKeysActivationError()
            throws KeyManagementException, IOException,
            InterruptedException, TimeoutException, SecureLoggerException,
            ApplicationException {
        doThrow(new KeyManagementException("test")).when(keysActivator)
            .activateNodeKeys(any(), any(), any());
        Future<Void> future = startApplication();
        try {
            future.get(1, TimeUnit.SECONDS);
        } catch (ExecutionException e) {
            verify(loggerManager, never()).openSecureLogger();
            throw (ApplicationException) e.getCause();
        }
    }

    @Test(expected = ApplicationException.class)
    public void testDoRunOpenSecureLoggerError()
            throws ApplicationException, InterruptedException,
            TimeoutException, SecureLoggerException {
        doThrow(new SecureLoggerException("test")).when(loggerManager)
            .openSecureLogger();
        Future<Void> future = startApplication();
        application.shutdown();
        try {
            future.get(1, TimeUnit.SECONDS);
        } catch (ExecutionException e) {
            assertFalse(application.isActivated());
            throw (ApplicationException) e.getCause();
        }
    }

    private Future<Void> startApplication() {
        return executor.submit(() -> {
            application.run(new String[0]);
            return null;
        });
    }

    @Configuration
    public static class TestableApplication
            extends AbstractControlComponentApplication {
        private boolean activated;

        private ApplicationException activateException;

        private boolean deactivated;

        private ApplicationException deactivateException;

        public boolean isActivated() {
            return activated;
        }

        public boolean isDeactivated() {
            return deactivated;
        }

        @Bean
        public NodeKeysActivator nodeKeysActivator() {
            return mock(NodeKeysActivator.class);
        }

        @Bean
        public SecureLoggerManager secureLoggerManager() {
            return mock(SecureLoggerManager.class);
        }

        public void setActivateException(
                ApplicationException activateException) {
            this.activateException = activateException;
        }

        public void setDeactivateException(
                ApplicationException deactivateException) {
            this.deactivateException = deactivateException;
        }

        @Override
        protected void activateMessageEndpoints()
                throws ApplicationException {
            if (activateException != null) {
                throw activateException;
            }
            activated = true;
        }

        @Override
        protected void deactivateMessageEndpoints()
                throws ApplicationException {
            if (deactivateException != null) {
                throw deactivateException;
            }
            deactivated = true;
        }
    }
}
