/*
 * 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.channel.cc.commons.services;

import java.security.KeyManagementException;
import java.security.cert.X509Certificate;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.products.ov.channel.cc.commons.keymanagement.ChoiceCodeKeysSpec;
import com.scytl.products.ov.channel.cc.commons.keymanagement.FingerprintGeneratorException;
import com.scytl.products.ov.channel.cc.commons.keymanagement.KeyManager;
import com.scytl.products.ov.channel.cc.commons.keymanagement.KeyNotFoundException;
import com.scytl.products.ov.commons.dto.CCPublicKey;
import com.scytl.products.ov.commons.dto.KeyCreationDTO;
import com.scytl.products.ov.commons.dto.KeyType;

@Service
public class ChoiceCodesKeyRepositoryImpl implements ChoiceCodesKeyRepository {

    private static final int GENERATION_KEY_LENGTH = 1;

    /**
     * 150 is the number of possible options per voting card set. For using the
     * appropriate amount of subkeys during the choice code decryption process,
     * only the correct number of subkeys have to be used, the rest have to be
     * multiplied together (and not discarded as in the mixing).
     */
    private static final int DECRYPTION_KEY_LENGTH = 150;

    @Autowired
    private KeyManager keyManager;

    @Value("${keys.nodeId:defCcxId}")
    private String controlComponentId;

    @Override
    @Transactional
	public void addGeneratedKey(KeyCreationDTO toBeReturned)
			throws GeneralCryptoLibException, KeyManagementException, FingerprintGeneratorException {
        if (!hasElectionSigningKey(toBeReturned.getElectionEventId(), toBeReturned.getFrom(), toBeReturned.getTo())) {
            generateElectionSigningKey(toBeReturned.getElectionEventId(), toBeReturned.getFrom(), toBeReturned.getTo());
        }

        if (!keyManager.hasChoiceCodeKeys(toBeReturned.getElectionEventId(), toBeReturned.getResourceId())) {
            ChoiceCodeKeysSpec keySpec = new ChoiceCodeKeysSpec.Builder().setDecryptionLength(DECRYPTION_KEY_LENGTH)
                .setElectionEventId(toBeReturned.getElectionEventId()).setGenerationLength(GENERATION_KEY_LENGTH)
                .setVerificationCardSetId(toBeReturned.getResourceId())
                .setParameters(ElGamalEncryptionParameters.fromJson(toBeReturned.getEncryptionParameters())).build();
            keyManager.createChoiceCodeKeys(keySpec);
        }
        X509Certificate signingCertificate =
            keyManager.getElectionSigningCertificate(toBeReturned.getElectionEventId());
        List<CCPublicKey> keyList = new ArrayList<>(2);

        ElGamalPublicKey generationKey = keyManager.getChoiceCodeGenerationPublicKey(toBeReturned.getElectionEventId(),
            toBeReturned.getResourceId());

        CCPublicKey generatedGenerationKey = new CCPublicKey();
        generatedGenerationKey.setKeytype(KeyType.CHOICE_CODE_GENERATION);
        generatedGenerationKey.setPublicKey(generationKey);
        generatedGenerationKey.setSignerCertificate(signingCertificate);
        generatedGenerationKey.setKeySignature(keyManager.getChoiceCodeGenerationPublicKeySignature(
            toBeReturned.getElectionEventId(), toBeReturned.getResourceId()));

        generatedGenerationKey.setNodeCACertificate(keyManager.nodeCACertificate());
        keyList.add(generatedGenerationKey);

        ElGamalPublicKey decryptionKey = keyManager.getChoiceCodeDecryptionPublicKey(toBeReturned.getElectionEventId(),
            toBeReturned.getResourceId());

        CCPublicKey generatedDecryptionKey = new CCPublicKey();
        generatedDecryptionKey.setKeytype(KeyType.CHOICE_CODE_DECRYPTION);
        generatedDecryptionKey.setPublicKey(decryptionKey);
        generatedDecryptionKey.setSignerCertificate(signingCertificate);
        generatedDecryptionKey.setKeySignature(keyManager.getChoiceCodeDecryptionPublicKeySignature(
            toBeReturned.getElectionEventId(), toBeReturned.getResourceId()));
        generatedDecryptionKey.setNodeCACertificate(keyManager.nodeCACertificate());
        keyList.add(generatedDecryptionKey);

        toBeReturned.setPublicKey(keyList);
    }

    @Override
    public ElGamalPrivateKey getGenerationPrivateKey(String electionEventId, String verificationCardSetId)
            throws KeyManagementException {
        return keyManager.getChoiceCodeGenerationPrivateKey(electionEventId, verificationCardSetId);
    }

    @Override
    public ElGamalPrivateKey getDecryptionPrivateKey(String electionEventId, String verificationCardSetId)
            throws KeyManagementException {
        return keyManager.getChoiceCodeDecryptionPrivateKey(electionEventId, verificationCardSetId);
    }

    @Override
    public ElGamalPublicKey getDecryptionPublicKey(String electionEventId, String verificationCardSetId)
            throws KeyManagementException {
        return keyManager.getChoiceCodeDecryptionPublicKey(electionEventId, verificationCardSetId);
    }

    private void generateElectionSigningKey(String electionEventId, ZonedDateTime from, ZonedDateTime to)
            throws KeyManagementException, FingerprintGeneratorException {
        keyManager.createElectionSigningKeys(electionEventId, from, to);
    }

    private boolean hasElectionSigningKey(String electionEventId, ZonedDateTime from, ZonedDateTime to)
            throws KeyManagementException {
        return keyManager.hasValidElectionSigningKeys(electionEventId, from, to);
    }

    @Override
    public ZpSubgroup getEncryptionParameters(String electionEventId, String verificationCardSetId)
            throws KeyManagementException {
        return (ZpSubgroup) keyManager.getChoiceCodeEncryptionParameters(electionEventId, verificationCardSetId).getGroup();
    }
    
    @Override
    public X509Certificate getPlatformCACertificate() throws KeyNotFoundException, KeyManagementException {
        return keyManager.getPlatformCACertificate();
    }
}
