package com.distributeddb.reno;

import java.util.ArrayList;
import java.util.concurrent.ThreadLocalRandom;
import java.util.Iterator;
import java.util.List;

import org.junit.Assert;
import org.junit.Test;

import com.distributeddb.errors.EncryptionError;
import com.distributeddb.utils.FileBlock;
import com.distributeddb.reno.crypto.CryptoBlock;
import com.distributeddb.reno.crypto.Hasher;
import static com.distributeddb.utils.Constants.*;

public final class DataManipulatorTest {

    private static final String KEY = "0123456789123456";
    private static final String FILE = ";alsijeflisjfeliajselitjalstesljal;seafslefasleiflwief";

    private static List<String> breakFile(final String file) {
        final List<String> fileSegments = new ArrayList<>();

        final int length = file.length();
        final int sizeOfSegment = (int) Math.ceil(length / NUM_BLOCKS);

        int index = 0;
        while (index < length) {
            final String chunk = file.substring(index, Math.min(index + sizeOfSegment, length));
            fileSegments.add(chunk);
            index += sizeOfSegment;
        }

        return fileSegments;
    }

    private static List<Long> getBlockOrderValues(final int numberOfBlocks) {
        final List<Long> blockOrders = new ArrayList<>();
        long maxLong = Long.MAX_VALUE - 1_000_000_000;

        final Long first = ThreadLocalRandom.current().nextLong(Long.MIN_VALUE, maxLong);
        blockOrders.add(first);

        for (int i = 1; i < numberOfBlocks; i++) {
            maxLong += (Long.MAX_VALUE - maxLong) / 2;

            final Long blockOrder = ThreadLocalRandom.current().nextLong(blockOrders.get(i-1) + 1, maxLong);
            blockOrders.add(blockOrder);
        }

        return blockOrders;
    }

    private static List<FileBlock> createBlocksTestHelper(final String secretKey, final String file) { 
        final List<FileBlock> blocks = new ArrayList<>();

        final List<String> splitFiles = breakFile(file);
        final int numBlocks = splitFiles.size();
        final List<Long> blockOrders = getBlockOrderValues(numBlocks);

        final Iterator<String> it1 = splitFiles.iterator();
        final Iterator<Long> it2 = blockOrders.iterator();

        while (it1.hasNext() && it2.hasNext()) {
            String encrypted = null;
            try {
                encrypted = CryptoBlock.encrypt(it1.next(), secretKey);
            } catch (Exception e) {
                Assert.fail("Error encrypting file in helper");
            }
            final long blockOrder = it2.next();

            final FileBlock block = new FileBlock(blockOrder, encrypted);
            blocks.add(block);
        }

        return blocks;
    }

    public static List<String> createKeysTestHelper(final String secretKey, final String filename, final String user, final long fileSizeInBlocks) {
        final List<String> keys = new ArrayList<>();

        for (long blockNum = 0; blockNum < fileSizeInBlocks; blockNum++) {
            final String key = Hasher.createBlockKey(secretKey, filename, blockNum);
            keys.add(key);
        }

        return keys;

    }

    @Test
    public void testCreateBlocks() throws EncryptionError {
        final List<FileBlock> actual = DataManipulator.createBlocks(KEY, FILE);
        final List<FileBlock> expected = createBlocksTestHelper(KEY, FILE);

        Assert.assertEquals(expected.size(), actual.size());

        for (int i = 0; i < actual.size(); i++) {
            Assert.assertEquals(expected.get(i).getData(), actual.get(i).getData());
        }
    }

    @Test
    public void testMakeFile() throws EncryptionError {
        final List<FileBlock> brokenUp = createBlocksTestHelper(KEY, FILE);
        final String actual = DataManipulator.makeFile(brokenUp, KEY);

        Assert.assertEquals(FILE, actual);
    }

    @Test
    public void testCreateKeys() {
        final List<String> expected = createKeysTestHelper(KEY, FILE, USER, 10);
        final List<String> actual = DataManipulator.createKeys(KEY, FILE, USER, 10);

        Assert.assertEquals(10, actual.size());
        Assert.assertEquals(expected.size(), actual.size());
        for (int i = 0; i < expected.size(); i++) {
            Assert.assertEquals(expected.get(i), actual.get(i));
        } 
    }
}
