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

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

import javax.transaction.Transactional;

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

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.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.channel.cc.commons.keymanagement.MixingKeysSpec;
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 KeyRepositoryImpl implements KeyRepository {

    /**
     * The theoretical number of ElGamal subkeys to be used for supporting
     * write-ins in sVote is 61 (60 write-ins plus one subkey for options). 150
     * is chosen as an arbitrary number that is big enough to cover the
     * possibility of upgrading the smart-cards to use ones with double memory
     * size, and that the control component code does not have to be modified.
     * For using the appropriate amount of subkeys during the mixing process,
     * only the correct number of subkeys have to be used, the rest have to be
     * discarded (and not multiplied together as in the choice code decryption).
     */
    private static final int MIXING_KEY_LENGTH = 150;

    @Autowired
    private KeyManager keyManager;

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

    @Override
    public ElGamalPrivateKey getBallotDecryptionKey(String electionId, String electoralBoardId)
            throws KeyNotFoundException, KeyManagementException, GeneralCryptoLibException {
        return keyManager.getMixingPrivateKey(electionId, electoralBoardId);
    }

    @Override
    public ElGamalPublicKey getBallotEncryptionKey(String electionId, String electoralBoardId)
            throws KeyNotFoundException, KeyManagementException, GeneralCryptoLibException {
        return keyManager.getMixingPublicKey(electionId, electoralBoardId);
    }

    @Override
    public PrivateKey getSigningKey(String electionEventId) throws KeyNotFoundException, KeyManagementException {
        return keyManager.getElectionSigningPrivateKey(electionEventId);
    }

    @Override
    public X509Certificate[] getVerificationCertificateChain(String electionEventId)
            throws KeyNotFoundException, KeyManagementException {
        // The chain currently consists of the verification key certificate and
        // the node CA certificate.
        return new X509Certificate[] {keyManager.getElectionSigningCertificate(electionEventId),
                keyManager.nodeCACertificate() };
    }

    @Override
    public X509Certificate getPlatformCACertificate() throws KeyNotFoundException, KeyManagementException {
        // The chain currently consists only of the verification key
        // certificate.
        return keyManager.getPlatformCACertificate();
    }

    @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.hasMixingKeys(toBeReturned.getElectionEventId(), toBeReturned.getResourceId())) {
            MixingKeysSpec keySpec = new MixingKeysSpec.Builder().setElectionEventId(toBeReturned.getElectionEventId())
                .setLength(MIXING_KEY_LENGTH).setElectoralAuthorityId(toBeReturned.getResourceId())
                .setParameters(ElGamalEncryptionParameters.fromJson(toBeReturned.getEncryptionParameters())).build();
            keyManager.createMixingKeys(keySpec);

        }
        X509Certificate signingCertificate =
            keyManager.getElectionSigningCertificate(toBeReturned.getElectionEventId());
        ElGamalPublicKey mixingPublicKey =
            keyManager.getMixingPublicKey(toBeReturned.getElectionEventId(), toBeReturned.getResourceId());
        List<CCPublicKey> keyList = new ArrayList<>(1);
        CCPublicKey generatedKey = new CCPublicKey();
        generatedKey.setKeytype(KeyType.MIXING);
        generatedKey.setPublicKey(mixingPublicKey);
        generatedKey.setSignerCertificate(signingCertificate);
        generatedKey.setKeySignature(
            keyManager.getMixingPublicKeySignature(toBeReturned.getElectionEventId(), toBeReturned.getResourceId()));
        generatedKey.setNodeCACertificate(keyManager.nodeCACertificate());
        keyList.add(generatedKey);
        toBeReturned.setPublicKey(keyList);

    }

    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);
    }

}
