/*
 * 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;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.products.ov.commons.beans.TestBean;
import com.scytl.products.ov.commons.beans.TestBeanOneField;
import com.scytl.products.ov.commons.beans.TestConstructorBean;
import com.scytl.products.ov.commons.beans.TestNestedBean;
import com.scytl.products.ov.commons.configuration.JSONSignatureConfiguration;
import com.scytl.products.ov.commons.sign.JSONSigner;
import com.scytl.products.ov.commons.verify.JSONVerifier;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import org.apache.commons.codec.binary.Base64;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.nio.charset.StandardCharsets;
import java.security.KeyPair;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

public class JSONSignAndVerifyITest {

	public static final String FOO_BAR = "fooBar";

	public static final int TWO = 2;

	public static final String DOT = ".";

	public static final int THREE = 3;

	private static TestBean bean;

	private static TestBeanOneField beanWithOneField;

	private static TestNestedBean nestedBean;

	private static TestConstructorBean constructorBean;

	private static KeyPair keyPairForSigning;

	@Rule
	public ExpectedException expectedException = ExpectedException.none();

	@BeforeClass
	public static void setUp() throws GeneralCryptoLibException {

		bean = new TestBean();
		bean.setFieldOne(FOO_BAR);
		bean.setFieldTwo(TWO);

		beanWithOneField = new TestBeanOneField();
		beanWithOneField.setFieldOne(FOO_BAR);

		constructorBean = new TestConstructorBean(FOO_BAR, TWO);

		nestedBean = new TestNestedBean();
		nestedBean.setBean(bean);
		nestedBean.setIndex(THREE);

		AsymmetricService asymmetricService = new AsymmetricService();
		keyPairForSigning = asymmetricService.getKeyPairForSigning();
	}

	@Test
	public void test_that_signs_and_verifies() {

		JSONSigner signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
		JSONVerifier verifier = new JSONVerifier();

		String signedJSON = signer.sign(keyPairForSigning.getPrivate(), bean);

		assertThat(Jwts.parser().isSigned(signedJSON), is(true));

		TestBean recoveredBean = verifier.verify(keyPairForSigning.getPublic(), signedJSON, TestBean.class);

		assertThat(recoveredBean.getFieldOne().equals(FOO_BAR), is(true));
		assertThat(recoveredBean.getFieldTwo() == TWO, is(true));
	}

	@Test
	public void test_that_signs_and_verifies_a_bean_with_constructor_instead_of_setters() {

		JSONSigner signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
		JSONVerifier verifier = new JSONVerifier();

		String signedJSON = signer.sign(keyPairForSigning.getPrivate(), constructorBean);

		assertThat(Jwts.parser().isSigned(signedJSON), is(true));

		TestBean recoveredBean = verifier.verify(keyPairForSigning.getPublic(), signedJSON, TestBean.class);

		assertThat(recoveredBean.getFieldOne().equals(FOO_BAR), is(true));
		assertThat(recoveredBean.getFieldTwo() == TWO, is(true));
	}

	@Test
	public void test_that_signs_and_verifies_bean_with_nested_fields() {

		JSONSigner signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
		JSONVerifier verifier = new JSONVerifier();

		String signedJSON = signer.sign(keyPairForSigning.getPrivate(), nestedBean);

		assertThat(Jwts.parser().isSigned(signedJSON), is(true));

		TestNestedBean recoveredBean = verifier.verify(keyPairForSigning.getPublic(), signedJSON, TestNestedBean.class);

		assertThat(recoveredBean.getBean() != null, is(true));
		assertThat(recoveredBean.getIndex() == THREE, is(true));
		assertThat(recoveredBean.getBean().getFieldOne().equals(FOO_BAR), is(true));
		assertThat(recoveredBean.getBean().getFieldTwo() == TWO, is(true));
	}

	@Test
	public void throw_exception_when_signs_completed_bean_and_verifies_bean_with_less_number_of_attributes() {

		JSONSigner signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
		JSONVerifier verifier = new JSONVerifier();

		String signedJSON = signer.sign(keyPairForSigning.getPrivate(), bean);

		expectedException.expect(IllegalArgumentException.class);
		verifier.verify(keyPairForSigning.getPublic(), signedJSON, TestBeanOneField.class);
	}

	@Test
	public void test_that_signs_bean_with_one_field_and_verifies_bean_with_two_fields() {

		JSONSigner signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
		JSONVerifier verifier = new JSONVerifier();

		String signedJSON = signer.sign(keyPairForSigning.getPrivate(), beanWithOneField);

		assertThat(Jwts.parser().isSigned(signedJSON), is(true));

		TestBean recoveredBean = verifier.verify(keyPairForSigning.getPublic(), signedJSON, TestBean.class);

		assertThat(recoveredBean.getFieldOne().equals(FOO_BAR), is(true));
		assertThat(recoveredBean.getFieldTwo() == null, is(true));
	}

	@Test
	public void test_that_signs_and_signed_json_without_dots_throws_parsing_exception() {

		JSONSigner signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
		JSONVerifier verifier = new JSONVerifier();

		String signedJSON = signer.sign(keyPairForSigning.getPrivate(), bean);

		String[] fields = signedJSON.split("\\.");
		String modifiedSignedJSON = fields[0] + fields[1] + fields[2];

		expectedException.expect(MalformedJwtException.class);
		verifier.verify(keyPairForSigning.getPublic(), modifiedSignedJSON, TestBean.class);
	}

	@Test
	public void test_that_signs_and_modified_header_throws_verification_exception() {

		JSONSigner signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
		JSONVerifier verifier = new JSONVerifier();

		String signedJSON = signer.sign(keyPairForSigning.getPrivate(), bean);

		String[] fields = signedJSON.split("\\.");
		String modifiedHeader = fields[0] + "A=";

		String modifiedSignedJSON = modifiedHeader + DOT + fields[1] + DOT + fields[2];

		expectedException.expect(SignatureException.class);
		verifier.verify(keyPairForSigning.getPublic(), modifiedSignedJSON, TestBean.class);
	}

	@Test
	public void test_that_signs_and_modified_payload_throws_verification_exception() {

		JSONSigner signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
		JSONVerifier verifier = new JSONVerifier();

		String signedJSON = signer.sign(keyPairForSigning.getPrivate(), bean);

		String[] fields = signedJSON.split("\\.");
		String modifiedPayload = fields[1] + "A=";

		String modifiedSignedJSON = fields[0] + DOT + modifiedPayload + DOT + fields[2];

		expectedException.expect(SignatureException.class);
		verifier.verify(keyPairForSigning.getPublic(), modifiedSignedJSON, TestBean.class);
	}

	@Test
	public void test_that_signs_and_if_corrupted_bean_is_introduced_as_payload_throws_verification_exception() {

		JSONSigner signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
		JSONVerifier verifier = new JSONVerifier();

		String signedJSON = signer.sign(keyPairForSigning.getPrivate(), bean);

		String[] fields = signedJSON.split("\\.");
		String modifiedPayload = new String(
			Base64.encodeBase64(decodeBase64(fields[1]).replace(FOO_BAR, "barBarFOO").getBytes(StandardCharsets.UTF_8)),
			StandardCharsets.UTF_8);

		String modifiedSignedJSON = fields[0] + DOT + modifiedPayload + DOT + fields[2];

		expectedException.expect(SignatureException.class);
		verifier.verify(keyPairForSigning.getPublic(), modifiedSignedJSON, TestBean.class);
	}

	@Test
	public void test_that_signs_and_modified_signature_throws_verification_exception() {

		JSONSigner signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
		JSONVerifier verifier = new JSONVerifier();

		String signedJSON = signer.sign(keyPairForSigning.getPrivate(), bean);

		String[] fields = signedJSON.split("\\.");
		String modifiedSignature = fields[2] + "A=";

		String modifiedSignedJSON = fields[0] + DOT + fields[1] + DOT + modifiedSignature;

		expectedException.expect(SignatureException.class);
		verifier.verify(keyPairForSigning.getPublic(), modifiedSignedJSON, TestBean.class);
	}

	private String decodeBase64(final String field) {
		return new String(Base64.decodeBase64(field), StandardCharsets.UTF_8);
	}
}
