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

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.SecretKey;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.json.JsonObject;
import javax.json.JsonString;

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

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.api.symmetric.SymmetricServiceAPI;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIDerivedKey;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIKDFDeriver;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.CryptographicOperationException;
import com.scytl.products.ov.commons.crypto.SecretKeyForObjectRepository;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.vv.domain.model.content.CodesMappingEntity;
import com.scytl.products.ov.vv.domain.model.content.CodesMappingRepository;
import com.scytl.products.ov.vv.domain.model.verification.Verification;
import com.scytl.products.ov.vv.domain.model.verification.VerificationRepository;

/**
 * Retrieve short choice codes using long choice codes
 */
@Stateless
public class ShortCodesService {

    // The verification repository that will give us the verification card set
    // id for the voter verification card id
    @Inject
    private VerificationRepository verificationRepository;

    // The secret key repository that will give us the key to concatenate with
    // the long codes data
    @Inject
    private SecretKeyForObjectRepository secretKeyRepository;

    private static final String ALIAS = "codessk";

    // The repository where the choice code mappings are stored
    @Inject
    private CodesMappingRepository codesMappingRepository;

    // The service that will be used to decrypt the short codes
    @Inject
    private SymmetricServiceAPI symmetricService;

    // The service that will be used to calculate the long codes
    @Inject
    private PrimitivesServiceAPI primitivesService;

    /**
     * Retrieves the short codes to be shown regarding the long codes
     * calculated.
     * 
     * @param tenantId
     *            - tenant identifier.
     * @param eeid
     *            - election event identifier.
     * @param verificationCardId
     *            - verification card id
     * @param longCodes
     *            - long codes of the vote
     * @return
     * @throws ResourceNotFoundException
     * @throws CryptographicOperationException
     */
    public List<String> retrieveShortCodes(String tenantId, String eeid, String verificationCardId,
            List<byte[]> longCodes) throws ResourceNotFoundException, CryptographicOperationException {

        List<String> shortCodes = new ArrayList<>();

        Verification verification =
            verificationRepository.findByTenantIdElectionEventIdVerificationCardId(tenantId, eeid, verificationCardId);
        String verificationCardSetId = verification.getVerificationCardSetId();
        SecretKey codesKey =
            secretKeyRepository.findByTenantEEIDObjectIdAlias(tenantId, eeid, verificationCardSetId, ALIAS);
        byte[] codesKeyBytes = codesKey.getEncoded();
        int codesKeyBytesLength = codesKeyBytes.length;

        CodesMappingEntity codesMapping =
            codesMappingRepository.findByTenantIdElectionEventIdVerificationCardId(tenantId, eeid, verificationCardId);

        String mappingString = new String(Base64.decodeBase64(codesMapping.getJson()));
        JsonObject mappingForCard = JsonUtils.getJsonObject(mappingString);

        CryptoAPIKDFDeriver kdfDeriver = primitivesService.getKDFDeriver();
        try {
            for (byte[] longCode : longCodes) {
                byte[] hashedLongCodeBytes = primitivesService.getHash(longCode);
                String hashedLongCode = new String(Base64.encodeBase64(hashedLongCodeBytes), StandardCharsets.UTF_8);
                JsonString encryptedShortCodeJson = mappingForCard.getJsonString(hashedLongCode);
                if (encryptedShortCodeJson == null) {
                    String errorMessage = String.format(
                        "Encrypted short code not found for tenant: %s, election event id: %s and verification card id: %s",
                        tenantId, eeid, verificationCardId);
                    throw new ResourceNotFoundException(errorMessage);
                }
                String encryptedShortCode = encryptedShortCodeJson.getString();
                byte[] encryptedShortCodeBytes = Base64.decodeBase64(encryptedShortCode);

                int longCodeLength = longCode.length;
                byte[] derivedKeySeed = new byte[longCodeLength + codesKeyBytesLength];
                System.arraycopy(longCode, 0, derivedKeySeed, 0, longCodeLength);
                System.arraycopy(codesKeyBytes, 0, derivedKeySeed, longCodeLength, codesKeyBytesLength);

                byte[] derivedKeySeedHash = primitivesService.getHash(derivedKeySeed);
                CryptoAPIDerivedKey derivedKey = kdfDeriver.deriveKey(derivedKeySeedHash, derivedKeySeedHash.length);
                SecretKey shortCodeKey = symmetricService.getSecretKeyForEncryptionFromDerivedKey(derivedKey);
                byte[] shortCodeBytes = symmetricService.decrypt(shortCodeKey, encryptedShortCodeBytes);
                String shortCodeString = new String(shortCodeBytes);
                if (shortCodes.contains(shortCodeString)) {
                    throw new CryptographicOperationException("Duplicated short choice code retrieved");
                }
                shortCodes.add(shortCodeString);
            }

            return shortCodes;

        } catch (GeneralCryptoLibException e) {
            throw new CryptographicOperationException("Error calculating short choice codes:", e);
        }
    }

}
