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

import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.junit.Assert;
import org.junit.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.commons.beans.domain.model.Information.VoterInformation;
import com.scytl.products.ov.commons.beans.domain.model.authentication.AuthenticationToken;
import com.scytl.products.ov.commons.beans.domain.model.authentication.ExtendedAuthenticationUpdate;
import com.scytl.products.ov.commons.beans.domain.model.authentication.ExtendedAuthenticationUpdateRequest;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.AuthTokenValidationException;
import com.scytl.products.ov.commons.beans.exceptions.ExtendedAuthValidationException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.ValidationErrorType;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.util.HttpRequestService;
import com.scytl.products.ov.ea.domain.actions.ValidateExtendedAuthUpdateAction;
import com.scytl.products.ov.ea.domain.model.EncryptedSVK;
import com.scytl.products.ov.ea.domain.model.extendedauthentication.AllowedAttemptsExceededException;
import com.scytl.products.ov.ea.domain.model.extendedauthentication.ExtendedAuthenticationRepository;
import com.scytl.products.ov.ea.services.infrastructure.persistence.AuthenticationException;
import com.scytl.products.ov.ea.services.infrastructure.persistence.ExtendedAuthenticationService;

public class ExtendedAuthenticationResourceTest extends ExtendedAuthenticationResource {

    public ExtendedAuthenticationResourceTest() {
        super();
        this.extendedAuthenticationRepository = mock(ExtendedAuthenticationRepository.class);
        this.extendedAuthenticationService = mock(ExtendedAuthenticationService.class);
        this.trackIdInstance = mock(TrackIdInstance.class);
        this.secureLoggerWriter = mock(SecureLoggingWriter.class);
        this.transactionInfoProvider = mock(TransactionInfoProvider.class);
        this.httpRequestService = mock(HttpRequestService.class);
        this.validateExtendedAuthUpdateAction = mock(ValidateExtendedAuthUpdateAction.class);

    }

    @Test
    public void setSuccessfulResponse()
            throws ResourceNotFoundException, AllowedAttemptsExceededException, AuthenticationException,
            ApplicationException, GeneralCryptoLibException {

        when(extendedAuthenticationService.authenticate(any(), any(), any(), any()))
            .thenReturn(new EncryptedSVK("authenticated"));

        ExtendedAuthentication extendedAuthentication = new ExtendedAuthentication();
        extendedAuthentication.setExtraParam("eP");
        extendedAuthentication.setAuthId("voCId2");
        HttpServletRequest request = mock(HttpServletRequest.class);
        Response authenticate = authenticate("1", "2", "trackid", extendedAuthentication, request);
        Assert.assertEquals(authenticate.getStatus(), Status.OK.getStatusCode());
        Assert.assertEquals(((ExtendedAuthResponse) authenticate.getEntity()).getEncryptedSVK(), "authenticated");
    }

    @Test
    public void setVotingCardNotFoundResponse()
            throws ResourceNotFoundException, AllowedAttemptsExceededException, AuthenticationException,
            ApplicationException, GeneralCryptoLibException {
        when(extendedAuthenticationService.authenticate(any(), any(), any(), any()))
            .thenThrow(new ResourceNotFoundException(""));
        ExtendedAuthentication extendedAuthentication = new ExtendedAuthentication();
        extendedAuthentication.setExtraParam("eP");
        extendedAuthentication.setAuthId("voCId2");
        HttpServletRequest request = mock(HttpServletRequest.class);
        Response authenticate = authenticate("1", "2", "trackid", extendedAuthentication, request);
        Assert.assertEquals(((ExtendedAuthResponse) authenticate.getEntity()).getResponseCode(),
            Status.NOT_FOUND.name());
    }

    @Test
    public void setAllowedAttemptsExceededResponse()
            throws ResourceNotFoundException, AllowedAttemptsExceededException, AuthenticationException,
            ApplicationException, GeneralCryptoLibException {
        when(extendedAuthenticationService.authenticate(any(), any(), any(), any()))
            .thenThrow(new AllowedAttemptsExceededException());
        ExtendedAuthentication extendedAuthentication = new ExtendedAuthentication();
        extendedAuthentication.setExtraParam("eP");
        extendedAuthentication.setAuthId("voCId2");
        HttpServletRequest request = mock(HttpServletRequest.class);
        Response authenticate = authenticate("1", "2", "trackid", extendedAuthentication, request);
        Assert.assertEquals(((ExtendedAuthResponse) authenticate.getEntity()).getResponseCode(),
            Status.FORBIDDEN.name());
        Assert.assertEquals(((ExtendedAuthResponse) authenticate.getEntity()).getNumberOfRemainingAttempts(), 0);
    }

    @Test
    public void setAllowedAttemptsExceededResponseWithInvalidSalt()
            throws ResourceNotFoundException, AllowedAttemptsExceededException, AuthenticationException,
            ApplicationException, GeneralCryptoLibException {
        when(extendedAuthenticationService.authenticate(any(), any(), any(), any()))
            .thenThrow(new AuthenticationException("invalid extra parameter", 0));
        ExtendedAuthentication extendedAuthentication = new ExtendedAuthentication();
        extendedAuthentication.setExtraParam("eP");
        extendedAuthentication.setAuthId("voCId2");
        HttpServletRequest request = mock(HttpServletRequest.class);
        Response authenticate = authenticate("1", "2", "trackid", extendedAuthentication, request);
        Assert.assertEquals(((ExtendedAuthResponse) authenticate.getEntity()).getResponseCode(),
            Status.FORBIDDEN.name());
    }

    @Test
    public void setDatabaseErrorException()
            throws ResourceNotFoundException, AllowedAttemptsExceededException, AuthenticationException,
            ApplicationException, GeneralCryptoLibException {
        when(extendedAuthenticationService.authenticate(any(), any(), any(), any()))
            .thenThrow(new ApplicationException("DbException"));
        ExtendedAuthentication extendedAuthentication = new ExtendedAuthentication();
        extendedAuthentication.setExtraParam("eP");
        extendedAuthentication.setAuthId("voCId2");
        HttpServletRequest request = mock(HttpServletRequest.class);
        Response authenticate = authenticate("1", "2", "trackid", extendedAuthentication, request);
        Assert.assertEquals(((ExtendedAuthResponse) authenticate.getEntity()).getResponseCode(),
            Status.NOT_FOUND.name());
    }

    @Test
    public void setAuthenticationExceptionResponse()
            throws ResourceNotFoundException, AllowedAttemptsExceededException, AuthenticationException,
            ApplicationException, GeneralCryptoLibException {
        when(extendedAuthenticationService.authenticate(any(), any(), any(), any()))
            .thenThrow(new AuthenticationException("", 1));
        ExtendedAuthentication extendedAuthentication = new ExtendedAuthentication();
        extendedAuthentication.setExtraParam("eP");
        extendedAuthentication.setAuthId("voCId2");
        HttpServletRequest request = mock(HttpServletRequest.class);
        Response authenticate = authenticate("1", "2", "trackid", extendedAuthentication, request);
        Assert.assertEquals(((ExtendedAuthResponse) authenticate.getEntity()).getResponseCode(),
            Status.UNAUTHORIZED.name());
    }

    @Test
    public void successfulUpdate() throws JsonProcessingException, ResourceNotFoundException, ApplicationException {
        AuthenticationToken authenticationTokenObject = createDummyAuthenticationToken();
        String authenticationToken = ObjectMappers.toJson(authenticationTokenObject);
        ExtendedAuthenticationUpdateRequest extendedAuthenticationUpdateRequest =
            new ExtendedAuthenticationUpdateRequest();
        ExtendedAuthenticationUpdate updateValidation =
            prepareUpdateValidationInfo(extendedAuthenticationUpdateRequest);
        HttpServletRequest request = mock(HttpServletRequest.class);
        when(validateExtendedAuthUpdateAction.validate(any(), any(), any(), any())).thenReturn(updateValidation);
        com.scytl.products.ov.ea.domain.model.extendedauthentication.ExtendedAuthentication extendedAuthentication =
            createFromRequest(updateValidation);
        when(extendedAuthenticationService.renewExistingExtendedAuthentication(any(), any(), any(), any(), any()))
            .thenReturn(extendedAuthentication);
        Response update =
            update("1", "2", "trackid", authenticationToken, extendedAuthenticationUpdateRequest, request);
        Assert.assertEquals(update.getStatus(), 200);
        Assert.assertEquals(((ExtendedAuthResponse) update.getEntity()).getResponseCode(), Status.OK.name());
    }

    private ExtendedAuthenticationUpdate prepareUpdateValidationInfo(
            ExtendedAuthenticationUpdateRequest extendedAuthenticationUpdateRequest) {
        extendedAuthenticationUpdateRequest.setCertificate("cert");
        extendedAuthenticationUpdateRequest.setSignature("signature");
        ExtendedAuthenticationUpdate updateValidation = new ExtendedAuthenticationUpdate();
        updateValidation.setAuthenticationTokenSignature("signature");
        updateValidation.setNewAuthID("newAuthId");
        updateValidation.setNewSVK("newSvk");
        updateValidation.setOldAuthID("oldAuthId");
        return updateValidation;
    }

    @Test
    public void updateWithInvalidAuthentication()
            throws JsonProcessingException, ResourceNotFoundException, ApplicationException {
        AuthenticationToken authenticationTokenObject = createDummyAuthenticationToken();
        String authenticationToken = ObjectMappers.toJson(authenticationTokenObject);
        ExtendedAuthenticationUpdateRequest extendedAuthenticationUpdateRequest =
            new ExtendedAuthenticationUpdateRequest();
        HttpServletRequest request = mock(HttpServletRequest.class);
        when(validateExtendedAuthUpdateAction.validate(any(), any(), any(), any()))
            .thenThrow(new ExtendedAuthValidationException(ValidationErrorType.AUTH_TOKEN_EXPIRED));
        Response update =
            update("1", "2", "trackid", authenticationToken, extendedAuthenticationUpdateRequest, request);
        Assert.assertEquals(update.getStatus(), 200);
        Assert.assertEquals(((ExtendedAuthResponse) update.getEntity()).getResponseCode(), Status.BAD_REQUEST.name());
    }

    @Test
    public void updateWithExpiredAuthentication()
            throws JsonProcessingException, ResourceNotFoundException, ApplicationException {
        AuthenticationToken authenticationTokenObject = createDummyAuthenticationToken();
        String authenticationToken = ObjectMappers.toJson(authenticationTokenObject);
        ExtendedAuthenticationUpdateRequest extendedAuthenticationUpdateRequest =
            new ExtendedAuthenticationUpdateRequest();
        HttpServletRequest request = mock(HttpServletRequest.class);
        when(validateExtendedAuthUpdateAction.validate(any(), any(), any(), any()))
            .thenThrow(new AuthTokenValidationException(ValidationErrorType.AUTH_TOKEN_EXPIRED));
        Response update =
            update("1", "2", "trackid", authenticationToken, extendedAuthenticationUpdateRequest, request);
        Assert.assertEquals(update.getStatus(), 200);
        Assert.assertEquals(((ExtendedAuthResponse) update.getEntity()).getResponseCode(), Status.BAD_REQUEST.name());
    }

    @Test
    public void updateWithResourceNotFound()
            throws JsonProcessingException, ResourceNotFoundException, ApplicationException {
        AuthenticationToken authenticationTokenObject = createDummyAuthenticationToken();
        String authenticationToken = ObjectMappers.toJson(authenticationTokenObject);
        ExtendedAuthenticationUpdateRequest extendedAuthenticationUpdateRequest =
            new ExtendedAuthenticationUpdateRequest();
        HttpServletRequest request = mock(HttpServletRequest.class);
        ExtendedAuthenticationUpdate updateValidation =
            prepareUpdateValidationInfo(extendedAuthenticationUpdateRequest);
        when(validateExtendedAuthUpdateAction.validate(any(), any(), any(), any())).thenReturn(updateValidation);
        when(extendedAuthenticationService.renewExistingExtendedAuthentication(any(), any(), any(), any(), any()))
            .thenThrow(new ResourceNotFoundException(""));
        Response update =
            update("1", "2", "trackid", authenticationToken, extendedAuthenticationUpdateRequest, request);
        Assert.assertEquals(update.getStatus(), 200);
        Assert.assertEquals(((ExtendedAuthResponse) update.getEntity()).getResponseCode(), Status.NOT_FOUND.name());
    }

    @Test
    public void updateWithInvalidSvk() throws JsonProcessingException, ResourceNotFoundException, ApplicationException {
        AuthenticationToken authenticationTokenObject = createDummyAuthenticationToken();
        String authenticationToken = ObjectMappers.toJson(authenticationTokenObject);
        ExtendedAuthenticationUpdateRequest extendedAuthenticationUpdateRequest =
            new ExtendedAuthenticationUpdateRequest();
        HttpServletRequest request = mock(HttpServletRequest.class);
        ExtendedAuthenticationUpdate updateValidation =
            prepareUpdateValidationInfo(extendedAuthenticationUpdateRequest);
        when(validateExtendedAuthUpdateAction.validate(any(), any(), any(), any())).thenReturn(updateValidation);
        when(extendedAuthenticationService.renewExistingExtendedAuthentication(any(), any(), any(), any(), any()))
            .thenThrow(new ApplicationException(""));
        Response update =
            update("1", "2", "trackid", authenticationToken, extendedAuthenticationUpdateRequest, request);
        Assert.assertEquals(update.getStatus(), 200);
        Assert.assertEquals(((ExtendedAuthResponse) update.getEntity()).getResponseCode(), Status.CONFLICT.name());
    }

    private com.scytl.products.ov.ea.domain.model.extendedauthentication.ExtendedAuthentication createFromRequest(
            ExtendedAuthenticationUpdate updateValidation) {
        com.scytl.products.ov.ea.domain.model.extendedauthentication.ExtendedAuthentication updated =
            new com.scytl.products.ov.ea.domain.model.extendedauthentication.ExtendedAuthentication();
        updated.setAttempts(2);
        updated.setAuthId(updateValidation.getNewAuthID());
        updated.setEncryptedStartVotingKey(updateValidation.getNewSVK());
        return updated;
    }

    private AuthenticationToken createDummyAuthenticationToken() {
        VoterInformation voterInformation = new VoterInformation();
        voterInformation.setBallotBoxId("");
        voterInformation.setBallotId("");
        voterInformation.setCredentialId("");
        voterInformation.setElectionEventId("");
        voterInformation.setTenantId("");
        voterInformation.setVerificationCardId("");
        voterInformation.setVerificationCardSetId("");
        voterInformation.setVotingCardId("");
        voterInformation.setVotingCardSetId("");
        voterInformation.setVerificationCardSetId("");
        voterInformation.setVotingCardSetId("");
        AuthenticationToken authenticationToken = new AuthenticationToken();
        authenticationToken.setId("id");
        authenticationToken.setSignature("signature");
        authenticationToken.setTimestamp("timestamp");
        authenticationToken.setVoterInformation(voterInformation);
        return authenticationToken;
    }

}
