/*
 * 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.commons.infrastructure.remote.filter;

import static org.mockito.Mockito.*;

import java.io.IOException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.products.ov.commons.exception.OvCommonsSignException;
import com.scytl.products.ov.commons.infrastructure.exception.OvCommonsInfrastructureException;
import com.scytl.products.ov.commons.infrastructure.remote.client.NodeIdentifier;
import com.scytl.products.ov.commons.infrastructure.remote.client.RestClientInterceptor;
import com.scytl.products.ov.commons.sign.RequestSigner;
import com.scytl.products.ov.commons.sign.beans.SignedRequestContent;
import com.scytl.products.ov.commons.verify.RequestVerifier;

import mockit.Mock;
import mockit.MockUp;

/**
 * @author rmarquez
 * @date 25/08/16 Copyright (C) 2016 Scytl Secure Electronic Voting SA All rights reserved.
 */
public class SignedRequestFilterTest {

    @BeforeClass
    public static void runOnceSetup() {
        Security.addProvider( new BouncyCastleProvider() );
    }

    @Test
    public void testFilterLetsTheRequestToContinue()
            throws IOException, ServletException, OvCommonsSignException, GeneralCryptoLibException {
        // create the objects to be mocked
        HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
        HttpServletResponse httpServletResponse = mock(HttpServletResponse.class);
        FilterChain filterChain = mock(FilterChain.class);

        // Create the request content
        String url = "/test";
        String method = "GET";
        String body = "1..2..3, This is the request body!";
        NodeIdentifier nodeIdentifier = NodeIdentifier.CONFIG_PLATFORM_ROOT;
        String requestOriginatorName = nodeIdentifier.name();
        SignedRequestContent signedRequestContent = new SignedRequestContent(url, method, body, requestOriginatorName);

        // Generate keys to be used
        AsymmetricService asymmetricService = new AsymmetricService();
        KeyPair keyPair = asymmetricService.getKeyPairForSigning();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();

        // Generate request signature and verify it
        RequestSigner requestSigner = new RequestSigner();
        RequestVerifier requestVerifier = new RequestVerifier();
        byte[] signature = requestSigner.sign(signedRequestContent, privateKey);
        String signatureStrEnc = new String(Base64.getEncoder().encode(signature));
        boolean verified = requestVerifier.verifySignature(signedRequestContent, signature, publicKey);

        Assert.assertTrue(verified);

        when(httpServletRequest.getRequestURI()).thenReturn(url);
        when(httpServletRequest.getMethod()).thenReturn(method);
        when(httpServletRequest.getHeader(RestClientInterceptor.HEADER_SIGNATURE)).thenReturn(signatureStrEnc);
        when(httpServletRequest.getHeader(RestClientInterceptor.HEADER_ORIGINATOR)).thenReturn(requestOriginatorName);

        new MockUp<SignedRequestFilter>() {
            @Mock
            PublicKey getPublicKey(final NodeIdentifier nodeIdentifier) throws OvCommonsInfrastructureException {
                return publicKey;
            }

            @Mock
            String getRequestBodyToString(final MultiReadHttpServletRequest multiReadHttpServletRequest) {
                return body;
            }

            @Mock
            private boolean applySignatureVerificationToUrl(String url) {
                return true;
            }
        };

        SignedRequestFilter signedRequestFilter = new SignedRequestFilter();
        signedRequestFilter.doFilter(httpServletRequest, httpServletResponse, filterChain);

        // Verify if the response has no more interactions meaning passed the filter.
        Mockito.verifyNoMoreInteractions(httpServletResponse);

    }

    @Test
    public void testFilterRequest401()
            throws IOException, ServletException, OvCommonsSignException, GeneralCryptoLibException {
        // create the objects to be mocked
        HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
        HttpServletResponse httpServletResponse = mock(HttpServletResponse.class);
        FilterChain filterChain = mock(FilterChain.class);

        String url = "/test";
        String method = "GET";
        String body = "1..2..3, This is the request body!";
        String originator = NodeIdentifier.CONFIG_PLATFORM_ROOT.name();

        AsymmetricService asymmetricService = new AsymmetricService();
        KeyPair keyPair = asymmetricService.getKeyPairForSigning();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();

        SignedRequestContent signedRequestContent =
            new SignedRequestContent("/wrongUrlToFail", method, body, originator);

        RequestSigner requestSigner = new RequestSigner();
        byte[] signature = requestSigner.sign(signedRequestContent, privateKey);
        String signatureStrEnc = new String(Base64.getEncoder().encode(signature));

        RequestVerifier requestVerifier = new RequestVerifier();
        boolean verified = requestVerifier.verifySignature(signedRequestContent, signature, publicKey);

        Assert.assertTrue(verified);

        when(httpServletRequest.getRequestURI()).thenReturn(url);
        when(httpServletRequest.getMethod()).thenReturn(method);
        when(httpServletRequest.getHeader(RestClientInterceptor.HEADER_SIGNATURE)).thenReturn(signatureStrEnc);
        when(httpServletRequest.getHeader(RestClientInterceptor.HEADER_ORIGINATOR))
            .thenReturn(NodeIdentifier.ADMIN_PORTAL.name());

        new MockUp<SignedRequestFilter>() {
            @Mock
            PublicKey getPublicKey(final NodeIdentifier nodeIdentifier) throws OvCommonsInfrastructureException {
                return publicKey;
            }

            @Mock
            private boolean applySignatureVerificationToUrl(String url) {
                return true;
            }

        };

        SignedRequestFilter signedRequestFilter = new SignedRequestFilter();
        signedRequestFilter.doFilter(httpServletRequest, httpServletResponse, filterChain);

        // verify if a setStatus() was performed with the expected code.
        verify(httpServletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    }

    @Test
    public void testEnum() {

        NodeIdentifier nodeIdentifier;
        try {
            String originatorStr = NodeIdentifier.ADMIN_PORTAL.name();
            nodeIdentifier = NodeIdentifier.valueOf(originatorStr);

        } catch (IllegalArgumentException e) {
            return;
        }
        Assert.assertNotNull(nodeIdentifier);

        try {
            String originatorStr = "wrong-value";
            nodeIdentifier = NodeIdentifier.valueOf(originatorStr);

        } catch (IllegalArgumentException e) {
            nodeIdentifier = null;
        }

        Assert.assertNull(nodeIdentifier);

    }

    @Test
    public void testRegularExpressions() {
        Pattern p = Pattern.compile(".*secured.*|.*platformdata.*");

        Matcher m = p.matcher("/whatever/secured");
        boolean b = m.matches();
        Assert.assertTrue(b);

    }

}
