/*
 * 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.config.shares.handler;

import java.nio.charset.StandardCharsets;
import java.security.KeyException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashSet;
import java.util.Set;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.ov.config.shares.domain.ReadSharesOperationContext;
import com.scytl.products.ov.config.shares.exception.SharesException;
import com.scytl.products.ov.config.shares.exception.SmartcardException;
import com.scytl.products.ov.config.shares.keys.PrivateKeySerializer;
import com.scytl.products.ov.config.shares.service.SmartCardService;
import com.scytl.shares.ShamirAlgorithm;
import com.scytl.shares.Share;
import com.scytl.shares.ShareException;

public final class ReadSharesHandler {

    private final ReadSharesOperationContext context;

    private final Set<Share> shares;

    private final PrivateKeySerializer privateKeySerializer;

    private final SmartCardService smartcardService;

    private final AsymmetricServiceAPI asymmetricServiceAPI;

    public ReadSharesHandler(final ReadSharesOperationContext context, final PrivateKeySerializer privateKeySerializer,
            final SmartCardService smartcardService, AsymmetricServiceAPI asymmetricServiceAPI) {
        this.context = context;
        this.privateKeySerializer = privateKeySerializer;
        this.shares = new HashSet<>();
        this.smartcardService = smartcardService;
        this.asymmetricServiceAPI = asymmetricServiceAPI;
    }

    public void readShare(final String pin) throws SharesException {

        Share share;
        try {
            share = smartcardService.read(pin, context.getAuthoritiesPublicKey());
            shares.add(share);
        } catch (SmartcardException e) {
            throw new SharesException("An error occurred while reading the smartcard", e);
        }
    }

    public PrivateKey getPrivateKey() throws SharesException {

        ShamirAlgorithm sa = new ShamirAlgorithm();

        try {

            byte[] recovered = sa.recover(shares);

            PublicKey boardPublicKey = context.getBoardPublicKey();
            PrivateKey privateKey = privateKeySerializer.reconstruct(recovered, boardPublicKey);

            validateKeyPair(boardPublicKey, privateKey);

            return privateKey;

        } catch (ShareException e) {
            throw new SharesException("There was an error reconstructing the secret from the shares", e);
        } catch (KeyException e) {
            throw new SharesException("There was an error reconstructing the private key from the shares", e);
        }
    }

    public String readSmartcardLabel() throws SharesException {

        try {
            return smartcardService.readSmartcardLabel();
        } catch (SmartcardException e) {
            throw new SharesException("Error while trying to read the smartcard label", e);
        }
    }

    public boolean isSmartcardOk() {
        return smartcardService.isSmartcardOk();
    }

    public int getTotalNumberOfCards() {

        if (shares.isEmpty()) {
            return 0;
        }

        return shares.iterator().next().getNumberOfParts();
    }

    public int getThreshold() {

        if (shares.isEmpty()) {
            return 0;
        }

        return shares.iterator().next().getThreshold();
    }

    private void validateKeyPair(final PublicKey subjectPublicKey, final PrivateKey privateKey) throws SharesException {

        final String testString = "foobar";
        byte[] encryptedTestString = null;
        byte[] decryptedTestString = null;

        try {
            encryptedTestString =
                asymmetricServiceAPI.encrypt(subjectPublicKey, testString.getBytes(StandardCharsets.UTF_8));
            decryptedTestString = asymmetricServiceAPI.decrypt(privateKey, encryptedTestString);
        } catch (GeneralCryptoLibException e) {
            throw new SharesException(
                "There was an error while validating the reconstructed private key with the given public key", e);
        }
        final String decrypted = new String(decryptedTestString, StandardCharsets.UTF_8);

        if (!testString.equals(decrypted)) {
            throw new SharesException(
                "There was an error validating the reconstructed private key with the given public key");
        }
    }
}
