/*
 * 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.sdm.ui.ws.rs.application;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBException;

import com.scytl.product.ov.sdm.plugin.PhaseName;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.xml.sax.SAXException;

import com.scytl.product.ov.sdm.plugin.Plugins;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.sdm.application.service.ElectionEventService;
import com.scytl.products.ov.sdm.application.service.ExportImportService;
import com.scytl.products.ov.sdm.domain.model.operation.OperationsData;
import com.scytl.products.ov.sdm.domain.model.operation.OperationsOutputCode;
import com.scytl.products.ov.sdm.domain.model.operation.OperationResult;
import com.scytl.products.ov.sdm.plugin.ExecutionListener;
import com.scytl.products.ov.sdm.plugin.ResultCode;
import com.scytl.products.ov.sdm.plugin.SequentialExecutor;
import com.scytl.products.ov.sdm.plugin.XmlObjectsLoader;

@RunWith(MockitoJUnitRunner.class)
public class OperationsResourceTest{
    
	public static class OperationsResourceForTest extends OperationsResource{
		@Override
		protected List<String> getCommands(PhaseName phaseName) throws IOException, JAXBException, SAXException {
			List<String> result = new ArrayList<>();
			result.add("command");
			return result;
		}
	}

    @Mock
    private ExportImportService exportImportService;

    @Mock
    private ElectionEventService electionEventService;

    @Mock
    private PathResolver pathResolver;
    
    @Mock
    private Path pathMock;
    
    @Mock
    private XmlObjectsLoader xmlObjectsLoader;
    
    @Mock
    private Plugins plugins;
    
    @Mock
    private SequentialExecutor sequentialExecutor;

	@Mock
	private TransactionInfoProvider transactionInfoProvider;

	@Mock
	private SecureLoggingWriter secureLogger;

	@InjectMocks
    @Spy
	OperationsResource operationsResource = new OperationsResourceForTest();
	
	@Test
	public void testExportOperationPathParameterIsRequired() {
		
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		OperationsData requestBody = new OperationsData();
		
		ResponseEntity<OperationResult> exportOperation = 
				operationsResource.exportOperation(electionEventId, requestBody);
		
		assertTrue(exportOperation.getStatusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR));
		assertTrue(exportOperation.getBody().getError() == OperationsOutputCode.MISSING_PARAMETER.value());
	}
	
	@Test
	public void testExportOperationElectionEventNotFound() throws IOException, ResourceNotFoundException {
		String electionEventId = "aaa";
		OperationsData requestBody = new OperationsData();
		requestBody.setPath("testPath");
	
		when(pathResolver.resolve(anyString(), anyString())).thenReturn(pathMock);
		when(pathResolver.resolve(anyString())).thenReturn(pathMock);

		doThrow(new ResourceNotFoundException("Election Event not found"))
			.when(exportImportService).dumpDatabase(electionEventId);
		
		ResponseEntity<OperationResult> exportOperation = 
				operationsResource.exportOperation(electionEventId, requestBody);
		
		assertTrue(exportOperation.getStatusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR));
		assertTrue(exportOperation.getBody().getError() == OperationsOutputCode.MISSING_PARAMETER.value());
	}
	
	@Test
	public void testExportOperationWhenExportElectionEventError() throws IOException {
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		OperationsData requestBody = new OperationsData();
		requestBody.setPath("testPath");
		requestBody.setElectionEventData(true);
		
		when(pathResolver.resolve(anyString())).thenReturn(pathMock);
		when(pathMock.resolve(anyString())).thenReturn(pathMock);
		
		doThrow(new IOException("Export Election Event Error"))
			.when(exportImportService).exportElectionEventWithoutElectionInformation(anyString(), anyString(), anyString());

		when(electionEventService.getElectionEventAlias(anyString())).thenReturn("electionEventAlias");

		ResponseEntity<OperationResult> exportOperation = 
				operationsResource.exportOperation(electionEventId, requestBody);
		
		assertTrue(exportOperation.getStatusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR));
		assertTrue(exportOperation.getBody().getError() == OperationsOutputCode.ERROR_IO_OPERATIONS.value());
	}
	
	@Test
	public void testExportOperationHappyPath() {
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		OperationsData requestBody = new OperationsData();
		requestBody.setPath("testPath");
		
		when(pathResolver.resolve(anyString())).thenReturn(pathMock);
		when(pathMock.resolve(anyString())).thenReturn(pathMock);
		
		ResponseEntity<OperationResult> exportOperation = 
				operationsResource.exportOperation(electionEventId, requestBody);
		
		assertTrue(exportOperation.getStatusCode().equals(HttpStatus.OK));
		assertTrue(exportOperation.getBody() == null);
	}
	
	@Test
	public void testGeneratePreVotingOutputsOperationPrivateKeyInBase64Missing() {
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		OperationsData requestBody = new OperationsData();
		
		try {
			operationsResource.generatePreVotingOutputsOperation(electionEventId, requestBody);
			fail("Exception not thrown");
		} catch (IllegalArgumentException e) {
			assertTrue(e.getMessage().equals("PrivateKey parameter is required"));
		}
	}
	
	@Test
	public void testGeneratePreVotingOutputsOperationWhenCallbackError() {
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		OperationsData requestBody = new OperationsData();
		requestBody.setPrivateKeyInBase64("NDQ0NDQ0NDQ0NDQ0NA");
		
		when(pathResolver.resolve(anyString())).thenReturn(pathMock);
		when(pathMock.resolve(anyString())).thenReturn(pathMock);
		
		doAnswer(new Answer<ExecutionListener>() {
		    public ExecutionListener answer(InvocationOnMock invocation) {
		        Object[] args = invocation.getArguments();
		        ExecutionListener callback = (ExecutionListener) args[2];
		        callback.onError(ResultCode.GENERAL_ERROR.value());
		        callback.onMessage("Error");
		        return null;
		    }
		}).when(sequentialExecutor).execute(any(), any(), any(ExecutionListener.class));
		
		ResponseEntity<OperationResult> preVotingOutputs = 
				operationsResource.generatePreVotingOutputsOperation(electionEventId, requestBody);
		
		assertTrue(preVotingOutputs.getStatusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR));
		assertTrue(preVotingOutputs.getBody().getError() == ResultCode.GENERAL_ERROR.value());
	}
	
	@Test
	public void testGeneratePreVotingOutputsHappyPath() {
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		OperationsData requestBody = new OperationsData();
		requestBody.setPrivateKeyInBase64("NDQ0NDQ0NDQ0NDQ0NA");
		
		when(pathResolver.resolve(anyString())).thenReturn(pathMock);
		when(pathMock.resolve(anyString())).thenReturn(pathMock);
		
		ResponseEntity<OperationResult> preVotingOutputs = 
				operationsResource.generatePreVotingOutputsOperation(electionEventId, requestBody);
		
		assertTrue(preVotingOutputs.getStatusCode().equals(HttpStatus.OK));
		assertTrue(preVotingOutputs.getBody() == null);
	}
	
	@Test
	public void testGeneratePostVotingOutputsOperationPrivateKeyInBase64Missing() {
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		String ballotBoxStatus = "ballotBoxStatus";
		OperationsData requestBody = new OperationsData();
		
		try {
			operationsResource.generatePostVotingOutputsOperation(electionEventId, ballotBoxStatus, requestBody);
			fail("Exception not thrown");
		} catch (IllegalArgumentException e) {
			assertTrue(e.getMessage().equals("PrivateKey parameter is required"));
		}
	}
	
	@Test
	public void testGeneratePostVotingOutputsOperationErrorStatusNameNoEnumConstant() {
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		String ballotBoxStatus = "ballotBoxStatus";
		OperationsData requestBody = new OperationsData();
		requestBody.setPrivateKeyInBase64("NDQ0NDQ0NDQ0NDQ0NA");
		
		ResponseEntity<OperationResult> postVotingOutputs = 
				operationsResource.generatePostVotingOutputsOperation(electionEventId, ballotBoxStatus, requestBody);
		
		assertTrue(postVotingOutputs.getStatusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR));
		assertTrue(postVotingOutputs.getBody().getError() == OperationsOutputCode.ERROR_STATUS_NAME.value());
	}
	
	@Test
	public void testGeneratePostVotingOutputsOperationStatusNameErrorPhaseNameNull() {
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		String ballotBoxStatus = "new";
		OperationsData requestBody = new OperationsData();
		requestBody.setPrivateKeyInBase64("NDQ0NDQ0NDQ0NDQ0NA");
		
		ResponseEntity<OperationResult> postVotingOutputs = 
				operationsResource.generatePostVotingOutputsOperation(electionEventId, ballotBoxStatus, requestBody);
		
		assertTrue(postVotingOutputs.getStatusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR));
		assertTrue(postVotingOutputs.getBody().getError() == OperationsOutputCode.ERROR_STATUS_NAME.value());
	}
	
	@Test
	public void testGeneratePostVotingOutputsOperationWhenCallbackError() {
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		String ballotBoxStatus = "bb_downloaded";
		OperationsData requestBody = new OperationsData();
		requestBody.setPrivateKeyInBase64("NDQ0NDQ0NDQ0NDQ0NA");
		
		when(pathResolver.resolve(anyString())).thenReturn(pathMock);
		when(pathMock.resolve(anyString())).thenReturn(pathMock);
		
		doAnswer(new Answer<ExecutionListener>() {
		    public ExecutionListener answer(InvocationOnMock invocation) {
		        Object[] args = invocation.getArguments();
		        ExecutionListener callback = (ExecutionListener) args[2];
		        callback.onError(ResultCode.GENERAL_ERROR.value());
		        callback.onMessage("Error");
		        return null;
		    }
		}).when(sequentialExecutor).execute(any(), any(), any(ExecutionListener.class));
		
		ResponseEntity<OperationResult> postVotingOutputs = 
				operationsResource.generatePostVotingOutputsOperation(electionEventId, ballotBoxStatus, requestBody);
		
		assertTrue(postVotingOutputs.getStatusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR));
		assertTrue(postVotingOutputs.getBody().getError() == ResultCode.GENERAL_ERROR.value());
	}

	@Test
	public void testGeneratePostVotingOutputsOperationHappyPath() {
		String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
		String ballotBoxStatus = "bb_downloaded";
		OperationsData requestBody = new OperationsData();
		requestBody.setPrivateKeyInBase64("NDQ0NDQ0NDQ0NDQ0NA");
		
		when(pathResolver.resolve(anyString())).thenReturn(pathMock);
		when(pathMock.resolve(anyString())).thenReturn(pathMock);
		
		ResponseEntity<OperationResult> postVotingOutputs = 
				operationsResource.generatePostVotingOutputsOperation(electionEventId, ballotBoxStatus, requestBody);
		
		assertTrue(postVotingOutputs.getStatusCode().equals(HttpStatus.OK));
		assertTrue(postVotingOutputs.getBody() == null);
	}
	
	@Test
	public void testImportOperationPathParameterMissing() {
		OperationsData requestBody = new OperationsData();
		
		ResponseEntity<OperationResult> importOpertaion = 
				operationsResource.importOperation(requestBody);
		
		assertTrue(importOpertaion.getStatusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR));
		assertTrue(importOpertaion.getBody().getError() == OperationsOutputCode.MISSING_PARAMETER.value());
	}
	
	@Test
	public void testImportOperationLoadDatabaseError() throws IOException {
		OperationsData requestBody = new OperationsData();
		requestBody.setPath("testPath");
		
		when(pathResolver.resolve(anyString())).thenReturn(pathMock);
		when(pathMock.resolve(anyString())).thenReturn(pathMock);
		
		doThrow(new IOException("Error"))
			.when(exportImportService).importDatabase();
		
		ResponseEntity<OperationResult> importOperation =
				operationsResource.importOperation(requestBody);
		
		assertTrue(importOperation.getStatusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR));
		assertTrue(importOperation.getBody().getError() == OperationsOutputCode.ERROR_IO_OPERATIONS.value());
	}
	
	@Test
	public void testImportOperationWhenCallBackError() throws IOException {
		OperationsData requestBody = new OperationsData();
		requestBody.setPath("testPath");
		
		when(pathResolver.resolve(anyString())).thenReturn(pathMock);
		when(pathMock.resolve(anyString())).thenReturn(pathMock);
		
		doThrow(new IOException("Error"))
			.when(exportImportService).importData(anyString());
		
		ResponseEntity<OperationResult> importOpertaion = 
				operationsResource.importOperation(requestBody);
		
		assertTrue(importOpertaion.getStatusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR));
		assertTrue(importOpertaion.getBody().getError() == OperationsOutputCode.ERROR_IO_OPERATIONS.value());
	}
    
    @Test
    public void testImportOperationHappyPath() {
        OperationsData requestBody = new OperationsData();
        requestBody.setPath("testPath");
        
        when(pathResolver.resolve(anyString())).thenReturn(pathMock);
        when(pathMock.resolve(anyString())).thenReturn(pathMock);
        
        ResponseEntity<OperationResult> importOpertaion = 
                operationsResource.importOperation(requestBody);
        
        assertTrue(importOpertaion.getStatusCode().equals(HttpStatus.OK));
        assertTrue(importOpertaion.getBody() == null);
    }
    
    @Test
    public void testExtendedAuthenticationMappingDataOperation() {

        String electionEventId = "a9d805a0d9ef4deeb4b14838f7e8ed8a";
        
        when(pathResolver.resolve(anyString())).thenReturn(pathMock);
        when(pathMock.resolve(anyString())).thenReturn(pathMock);
        
        ResponseEntity<OperationResult> exportOperation = 
                operationsResource.extendedAuthenticationMappingDataOperation(electionEventId, new OperationsData());
        
        assertTrue(exportOperation.getStatusCode().equals(HttpStatus.OK));
        assertTrue(exportOperation.getBody() == null);
    }
	
}
