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

import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;

import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Timer;
import java.util.TimerTask;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;

/**
 * Tests of {@link CacheImpl}.
 */
public class CacheImplTest {
    private static final String ELECTION_EVENT_ID = "electionEventId";

    private static final String VERIFICATION_CARD_SET_ID =
        "verificationCardSetId";

    private static final String ELECTORAL_AUTHORITY_ID =
        "electoralAuthorityId";

    private static final Duration EXPIRY_PERIOD = Duration.ofSeconds(1);

    private TimerSpy timer;

    private CacheImpl cache;

    @Before
    public void setUp() throws Exception {
        timer = new TimerSpy();
        cache = new CacheImpl(timer, EXPIRY_PERIOD);
        cache.startup();
    }

    @After
    public void tearDown() {
        timer.cancel();
    }

    @Test
    public void testPutChoiceCodeKeys() throws GeneralCryptoLibException {
        ZpSubgroup group = new ZpSubgroup(BigInteger.valueOf(4),
            BigInteger.valueOf(7), BigInteger.valueOf(3));
        Exponent exponent = new Exponent(group.getQ(), BigInteger.ONE);
        ElGamalPrivateKey privateKey =
            new ElGamalPrivateKey(singletonList(exponent), group);
        ZpGroupElement element = group.getGenerator();
        ElGamalPublicKey publicKey =
            new ElGamalPublicKey(singletonList(element), group);
        byte[] publicKeySignature = {1, 2, 3 };

        ChoiceCodeKeys keys = new ChoiceCodeKeys.Builder()
            .setDecryptionKeys(privateKey, publicKey, publicKeySignature)
            .setGenerationKeys(privateKey, publicKey, publicKeySignature)
            .build();

        cache.putChoiceCodeKeys(ELECTION_EVENT_ID,
            VERIFICATION_CARD_SET_ID, keys);

        assertEquals(keys, cache.getChoiceCodeKeys(ELECTION_EVENT_ID,
            VERIFICATION_CARD_SET_ID));
    }

    @Test
    public void testPutElectionSigningKeys() {
        PrivateKey privateKey = mock(PrivateKey.class);
        X509Certificate[] certificateChain =
            {mock(X509Certificate.class) };

        ElectionSigningKeys keys =
            new ElectionSigningKeys(privateKey, certificateChain);

        cache.putElectionSigningKeys(ELECTION_EVENT_ID, keys);

        assertEquals(keys,
            cache.getElectionSigningKeys(ELECTION_EVENT_ID));
    }

    @Test
    public void testPutMixingKeys() throws GeneralCryptoLibException {
        ZpSubgroup group = new ZpSubgroup(BigInteger.valueOf(4),
            BigInteger.valueOf(7), BigInteger.valueOf(3));
        Exponent exponent = new Exponent(group.getQ(), BigInteger.ONE);
        ElGamalPrivateKey privateKey =
            new ElGamalPrivateKey(singletonList(exponent), group);
        ZpGroupElement element = group.getGenerator();
        ElGamalPublicKey publicKey =
            new ElGamalPublicKey(singletonList(element), group);
        byte[] publicKeySignature = {1, 2, 3 };

        MixingKeys keys =
            new MixingKeys(privateKey, publicKey, publicKeySignature);

        cache.putMixingKeys(ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID,
            keys);

        assertEquals(keys, cache.getMixingKeys(ELECTION_EVENT_ID,
            ELECTORAL_AUTHORITY_ID));
    }

    @Test
    public void testShutdown() {
        cache.shutdown();
        assertTrue(timer.isCancelled());
    }

    @Test
    public void testStartup() {
        assertEquals(EXPIRY_PERIOD.toMillis(), timer.getDelay());
        assertEquals(EXPIRY_PERIOD.toMillis(), timer.getPeriod());
    }

    @Test
    public void testExpiry() {
        PrivateKey privateKey = mock(PrivateKey.class);
        X509Certificate[] certificateChain =
            {mock(X509Certificate.class) };

        ElectionSigningKeys keys =
            new ElectionSigningKeys(privateKey, certificateChain);

        cache.putElectionSigningKeys(ELECTION_EVENT_ID, keys);

        try {
            Thread.sleep(EXPIRY_PERIOD.toMillis() * 3);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            fail("Thread is interrupted.");
        }

        assertNull(cache.getElectionSigningKeys(ELECTION_EVENT_ID));
    }

    private static class TimerSpy extends Timer {
        private boolean cancelled;

        private long delay;

        private long period;

        public boolean isCancelled() {
            return cancelled;
        }

        public long getDelay() {
            return delay;
        }

        public long getPeriod() {
            return period;
        }

        @Override
        public void cancel() {
            super.cancel();
            cancelled = true;
        }

        @Override
        public void schedule(TimerTask task, long delay, long period) {
            super.schedule(task, delay, period);
            this.delay = delay;
            this.period = period;
        }
    }
}
