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

import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.CryptographicOperationException;

import org.apache.commons.codec.binary.Base64;

/**
 * Service used to perform cryptographic signatures for a specific kind of objects (e.g., for ballot box).
 */
public class SignatureForObjectService {

	private final AsymmetricServiceAPI asymmetricService;

	private final PrivateKeyForObjectRepository privateKeyRepository;

	private final InputDataFormatterService inputDataFormatterService;

	/**
	 * Constructor of the object.
	 * 
	 * @param asymmetricService - the asymmetric service instance.
	 * @param privateKeyRepository - the private key repository instance.
	 * @param inputDataFormatterService the formatter that will be used to serialize the data to be signed. Use the same
	 *        formatter on signature verification to guarantee the same input is used for signature and verification
	 *        processes.
	 */
	public SignatureForObjectService(AsymmetricServiceAPI asymmetricService,
			PrivateKeyForObjectRepository privateKeyRepository, InputDataFormatterService inputDataFormatterService) {
		this.asymmetricService = asymmetricService;
		this.privateKeyRepository = privateKeyRepository;
		this.inputDataFormatterService = inputDataFormatterService;
	}

	/**
	 * Sign the input data using the private key identified by this tenant, election event and alias.
	 * 
	 * @param tenantId the identifier of the tenant.
	 * @param eeId the identifier of the election event.
	 * @param objectId the specific object identifier.
	 * @param alias the alias of the private key that will be used to perform the signature
	 * @param data the data to be signed. It will be serialized for signature using the input data formatter provided on
	 *        this instance constructor.
	 * @return the signature over the input data
	 * @throws CryptographicOperationException if an error occurs during the signature operation or recovering the private
	 *         key
	 * @throws ResourceNotFoundException if the private key identified by this tenant, election event id and alias can not
	 *         be found
	 */
	public byte[] sign(String tenantId, String eeId, String objectId, String alias, String... data)
			throws CryptographicOperationException, ResourceNotFoundException {

		PrivateKey privateKey = privateKeyRepository.findByTenantEEIDObjectIdAlias(tenantId, eeId, objectId, alias);

		byte[] signatureInput = inputDataFormatterService.concatenate(data);

		try {
			return asymmetricService.sign(privateKey, signatureInput);
		} catch (GeneralCryptoLibException e) {
			throw new CryptographicOperationException("Error performing a signature:", e);
		}
	}

	/**
	 * Gets the public key associated to a given alias within a certificate chain.
	 *
	 * @param certChain - chain of certificates to check
	 * @param alias - alias of the desired public key
	 * @return a Public Key
	 * @throws GeneralCryptoLibException
	 */
	public PublicKey getPublicKeyByAliasInCertificateChain(Certificate[] certChain, String alias)
			throws GeneralCryptoLibException {

		PublicKey result = null;
		for (Certificate c : certChain) {
			CryptoX509Certificate wrappedCertificate = new CryptoX509Certificate((X509Certificate) c);
			X509DistinguishedName subject = wrappedCertificate.getSubjectDn();
			String name = subject.getCommonName();
			if (name.equals(alias)) {
				result = c.getPublicKey();
				break;
			}
		}
		return result;
	}

	/**
	 * Returns the certificate chain as String associated to a given alias.
	 *
	 * @param certChain - A chain of certificates to be transformed.
	 * @return a Map<String> representing the chain of certificates
	 * @throws GeneralCryptoLibException
	 * @throws CryptographicOperationException
	 */
	public Map<String, String> getCertificatesAsMap(Certificate[] certChain) throws GeneralCryptoLibException,
			CryptographicOperationException {

		Map<String, String> certChainMap = new HashMap<>();
		try {
			for (Certificate certificate : certChain) {
				String certString =
						new StringBuilder("-----BEGIN CERTIFICATE-----")
								.append(Base64.encodeBase64String(certificate.getEncoded())).append("-----END CERTIFICATE-----")
								.toString();

				CryptoX509Certificate wrappedCertificate = new CryptoX509Certificate((X509Certificate) certificate);
				X509DistinguishedName subject = wrappedCertificate.getSubjectDn();
				String name = subject.getCommonName();

				certChainMap.put(name, certString);

			}

			return certChainMap;
		} catch (CertificateException e) {
			throw new CryptographicOperationException("An error occured encoding a certificate object:", e);
		}
	}

}
