import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.InvalidProtocolBufferException;
import junit.framework.TestCase;
import meerkat.Registry;
import meerkat.RegistryUtils.CollectionMessagesUtils;
import meerkat.RegistryUtils.RegistryTags;
import meerkat.bulletinboard.AsyncBulletinBoardClient;
import meerkat.bulletinboard.ThreadedBulletinBoardClient;
import meerkat.crypto.concrete.ECDSASignature;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import meerkat.protobuf.BulletinBoardAPI.MessageFilterList;
import meerkat.protobuf.VoterRegistry;
import meerkat.protobuf.VoterRegistry.GroupID;
import meerkat.protobuf.VoterRegistry.VoterID;
import meerkat.protobuf.VoterRegistry.VoterInfo;
import meerkat.protobuf.Voting;

import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;

/**
 * TODO: add logs prints for the tests to be clear what they are
 */

/**
 * Created by Vladimir Eliezer Tokarev on 1/16/2016.
 * Tests the Simple RegistryUtils contents
 * NOTE: for most of this tests to pass there should run BulletinBoardServer
 * that should be reachable on BULLETIN_BOARD_SERVER_ADDRESS
 */
public class SimpleRegistryTest extends TestCase {

    private ECDSASignature signer;
    private AsyncBulletinBoardClient bulletinBoardClient;
    private InputStream certStream;
    private SecureRandom random = new SecureRandom();

    public static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
    public static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt";
    public static String KEYFILE_PASSWORD = "secret";
    Semaphore jobSemaphore;

    class DummyRegistryCallBackHandler<T> implements meerkat.VoterRegistry.RegistryCallBack<T>{
        public int counter;
        public T data;

        public DummyRegistryCallBackHandler()
        {
            counter=0;
        }

        @Override
        public void HandleResult(T result) {
            counter++;
            data = result;
            jobSemaphore.release();
        }

        @Override
        public void HandleFailure(Throwable throwable) {
        }
    }

    public class DummyBulletinBoardCallBackHandler implements FutureCallback<List<BulletinBoardMessage>> {
        public  List<BulletinBoardMessage> messages;

        @Override
        public void onSuccess(List<BulletinBoardMessage> msg)
        {
            messages = msg;
            jobSemaphore.release();
        }

        @Override
        public void onFailure(Throwable t){
            messages = null;
            jobSemaphore.release();
        }
    }

    private void createCertificateStream()
    {
        this.certStream = getClass().getResourceAsStream(CERT1_PEM_EXAMPLE);
    }

    private void CommunicatorSetup() {
        bulletinBoardClient = new ThreadedBulletinBoardClient();
        String BULLETIN_BOARD_SERVER_ADDRESS = "http://localhost:8081/";
        bulletinBoardClient.init(Voting.BulletinBoardClientParams.newBuilder()
                .addBulletinBoardAddress(BULLETIN_BOARD_SERVER_ADDRESS)
                .setMinRedundancy((float) 1.0)
                .build());
    }

    private void SetSigner(){
        try {
            signer = new ECDSASignature();
            InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
            char[] password = KEYFILE_PASSWORD.toCharArray();

            KeyStore.Builder keyStore = signer.getPKCS12KeyStoreBuilder(keyStream, password);
            signer.loadSigningCertificate(keyStore);

            keyStream.close();
        }
        catch (Exception e){
            assert false : "The signer creation failed ";
        }
    }

    /**
     * Initialize RegistryUtils object
     */
    public void setUp() {
        SetSigner();
        CommunicatorSetup();
        createCertificateStream();
        jobSemaphore = new Semaphore(0);
    }

    /**
     * Checks if the creation of the registry have been successful
     */
    public void testSimpleRegistryCreation() {
        try {
            new Registry(signer, bulletinBoardClient, certStream);
        } catch (Exception e) {
            assert false : "While creating the RegistryUtils exception have been thrown " + e;
        }
    }

    /**
     * Counts the amount of messages from messages that have all the wanted tags inside
     * @param messages  List<VoterRegistryMessage>
     * @param tags  List<RegistryTags>
     * @return  integer that represent the amount of messages with wanted tags
     */
    private int countMessagesWithTags(List<BulletinBoardMessage> messages, List<String> tags)
    {
        int counter = 0 ;

        for (int i = 0 ; i < messages.size() ; i++) {
            BulletinBoardMessage message  = messages.get(i);
            int wantedTagsCounter = 0;

            for (int j = 0 ;j < tags.size() ; j++) {
                String tag = tags.get(j);
                if(CollectionMessagesUtils.GetTagByName(message.getMsg().getTagList(), tag)!=null){
                    wantedTagsCounter++;
                }
            }

            if(wantedTagsCounter == tags.size())
            {
                counter++;
            }
        }
        return counter;
    }

    /**
     * Test that add voter creates new correct bulletin board message and adds the voter
     */
    public void testAddVoter() throws InvalidProtocolBufferException, InterruptedException {
        DummyRegistryCallBackHandler<Boolean> handler = new DummyRegistryCallBackHandler<>();

        String id = new BigInteger(130, random).toString(32);
        String data = new BigInteger(130, random).toString(32);
        VoterInfo voterInfo = VoterInfo.newBuilder().setId(VoterID.newBuilder().setId(id)).setInfo(data).build();

        Registry registry = new Registry(signer, bulletinBoardClient, certStream);
        registry.AddVoter(voterInfo, handler);

        jobSemaphore.acquire();
        assertEquals(1, handler.counter );

        List<String> tags = new ArrayList<String>(){{ add(RegistryTags.VOTER_ENTRY_TAG);}};
        MessageFilterList filters = CollectionMessagesUtils.GenerateFiltersFromTags(tags);
        DummyBulletinBoardCallBackHandler bulletinHandler = new DummyBulletinBoardCallBackHandler();
        bulletinBoardClient.readMessages(filters, bulletinHandler);

        jobSemaphore.acquire();

        tags.add(id);

        int counter = countMessagesWithTags(bulletinHandler.messages, tags);
        assert counter == 1 : "The server don't have the new user data.";
    }

    /**
     * Test that set voted posts creates correct bulletin board message and sets that the user have been voted
     */
    public void testSetVoted() throws InvalidProtocolBufferException, InterruptedException {
        DummyRegistryCallBackHandler<Boolean> handler = new  DummyRegistryCallBackHandler<>();

        String id = new BigInteger(130, random).toString(32);
        VoterID voterInfo = VoterID.newBuilder().setId(id).build();

        Registry registry = new Registry(signer, bulletinBoardClient, certStream);
        registry.SetVoted(voterInfo, handler);

        jobSemaphore.acquire();
        assertEquals(1, handler.counter );

        List<String> tags = new ArrayList<String>(){{ add(RegistryTags.VOTE_ACTION_TAG);}};
        MessageFilterList filters = CollectionMessagesUtils.GenerateFiltersFromTags(tags);
        DummyBulletinBoardCallBackHandler bulletinHandler = new DummyBulletinBoardCallBackHandler();
        bulletinBoardClient.readMessages(filters, bulletinHandler);

        jobSemaphore.acquire();

        tags.add(id);
        int counter = countMessagesWithTags(bulletinHandler.messages, tags);
        assert counter == 1 : "The server don't have the new user id.";
    }

    /**
     * Test that get groups retrieves the right groups the user are in
     */
    public void testAddToGroup() throws InvalidProtocolBufferException, InterruptedException {
        DummyRegistryCallBackHandler<Boolean> handler = new  DummyRegistryCallBackHandler<>();

        String voterId = new BigInteger(130, random).toString(32);
        String groupId = new BigInteger(130, random).toString(32);
        VoterRegistry.VoterRegistryMessage voterInfo = VoterRegistry.VoterRegistryMessage.newBuilder()
                .setVoterID(VoterID.newBuilder().setId(voterId))
                .addGroupID(GroupID.newBuilder().setId(groupId)).build();

        Registry registry = new Registry(signer, bulletinBoardClient, certStream);
        registry.AddToGroups(voterInfo, handler);

        jobSemaphore.acquire();
        assertEquals(1, handler.counter);

        List<String> tags = new ArrayList<String>(){{add(RegistryTags.ADD_TO_GROUP_TAG);}};
        MessageFilterList filters = CollectionMessagesUtils.GenerateFiltersFromTags(tags);
        DummyBulletinBoardCallBackHandler bulletinHandler = new DummyBulletinBoardCallBackHandler();
        bulletinBoardClient.readMessages(filters, bulletinHandler);

        jobSemaphore.acquire();

        tags.add(voterId);
        tags.add(groupId);

        int counter = countMessagesWithTags(bulletinHandler.messages, tags);
        assert counter == 1 : "The server don't have the new user added to group.";
    }

    /**
     * Test that remove from group creates correct bulletin board message and removes the user from a group
     */
    public void testGetGroups() throws InvalidProtocolBufferException, InterruptedException {
        DummyRegistryCallBackHandler<List<BulletinBoardMessage>> handler =
                new  DummyRegistryCallBackHandler<>();

        String voterId = new BigInteger(130, random).toString(32);
        String groupId = new BigInteger(130, random).toString(32);
        VoterRegistry.VoterRegistryMessage voterInfo = VoterRegistry.VoterRegistryMessage.newBuilder()
                .setVoterID(VoterID.newBuilder().setId(voterId))
                .addGroupID(GroupID.newBuilder().setId(groupId)).build();

        this.certStream = getClass().getResourceAsStream(CERT1_PEM_EXAMPLE);

        Registry registry = new Registry(signer, bulletinBoardClient,certStream);
        registry.AddToGroups(voterInfo, handler);

        jobSemaphore.acquire();
        assertEquals(1, handler.counter );

        DummyRegistryCallBackHandler<List<String>> groupsHandler = new DummyRegistryCallBackHandler<>();
        registry.GetGroups(GroupID.newBuilder().setId(groupId).build(), groupsHandler);

        jobSemaphore.acquire(1);
        List<String> userGroups = groupsHandler.data;
        assert userGroups.contains(RegistryTags.GROUP_ID_TAG + groupId) :
                "The simple voter registry object does not retrieved right user groups";
    }

    /**
     * Test that the personal data outputted about the user is right
     */
    public void testGetPersonalIDDetails() throws InvalidProtocolBufferException, InterruptedException {
        DummyRegistryCallBackHandler<List<BulletinBoardMessage>> handler =
                new DummyRegistryCallBackHandler<>();

        String id = new BigInteger(130, random).toString(32);
        String data = new BigInteger(130, random).toString(32);
        VoterInfo voterInfo = VoterInfo.newBuilder().
                setId(VoterID.newBuilder().setId(id)).setInfo(data).build();

        Registry registry = new Registry(signer, bulletinBoardClient,  certStream);
        registry.AddVoter(voterInfo, handler);

        jobSemaphore.acquire();
        assertEquals(1, handler.counter );

        DummyRegistryCallBackHandler<BulletinBoardMessage> personalHandler = new DummyRegistryCallBackHandler<>();
        registry.GetPersonIDDetails(VoterID.newBuilder().setId(id).build(), personalHandler);

        jobSemaphore.acquire(1);
        assertEquals(RegistryTags.ID_TAG + id, CollectionMessagesUtils.GetTagByName(personalHandler
                .data.getMsg().getTagList(), RegistryTags.ID_TAG));
        assertTrue(CollectionMessagesUtils.GetTagByName(personalHandler.data.getMsg().getTagList(),
                RegistryTags.VOTER_DATA_TAG).contains(data));
    }
}