package repositories;

import core.ApplicationConfiguration;
import core.TaskType;
import helpers.CryptoTaskConfig;
import helpers.WebTaskConfig;
import objects.Flag;
import objects.Solution;
import objects.Team;
import objects.User;
import objects.tasks.Task;
import objects.tasks.TaskCryptoContent;
import objects.tasks.TaskWebContent;
import org.apache.commons.codec.binary.Hex;
import org.mongodb.morphia.Datastore;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Created by gpietrus on 20.02.2016.
 */
@Singleton
public class TasksRepository implements Repository
{

    private ApplicationConfiguration applicationConfiguration;
    private Datastore datastore;
    private TeamsRepository teamsRepository;
    private SolutionsRepository solutionsRepository;
    private UsersRepository usersRepository;
    private String salt = "SECURE_SALT"; //todo

    @Inject
    public TasksRepository(ApplicationConfiguration applicationConfiguration, Datastore datastore,
                           TeamsRepository teamsRepository, SolutionsRepository solutionsRepository,
                           UsersRepository usersRepository)
    {
        this.applicationConfiguration = applicationConfiguration;
        this.datastore = datastore;
        this.teamsRepository = teamsRepository;
        this.solutionsRepository = solutionsRepository;
        this.usersRepository = usersRepository;
        initialize();
    }

    public Task get(String taskName)
    { //todo: task name?
        return datastore.createQuery(Task.class)
                .filter("name", taskName)
                .get();
    }

    public Task get(int level)
    {
        return datastore.createQuery(Task.class)
                .filter("level", level)
                .get();
    }

    public List<Task> getAllPublic()
    {
        return datastore.createQuery(Task.class)
                .retrievedFields(true, "name", "level") //todo: move to configuration
                .asList();
    }

    public List<Task> getAll()
    {
        return datastore.createQuery(Task.class).asList();
    }

    @Override
    public void initialize()
    {
        List<CryptoTaskConfig> cryptoTasks = applicationConfiguration.getCryptoTasks();
        List<WebTaskConfig> webTasks = applicationConfiguration.getWebTasks();

        this.clean();

        cryptoTasks.forEach(cryptoTaskConfig -> {

            cryptoTaskConfig.getFlags().stream()
                    .map(Flag::new)
                    .collect(Collectors.toList());

            this.add(new Task(
                    cryptoTaskConfig.getName(),
                    cryptoTaskConfig.getLevel(),
                    TaskType.CRYPTO,
                    cryptoTaskConfig.getFlags().stream()
                            .map(Flag::new)
                            .collect(Collectors.toList()),
                    new TaskCryptoContent(cryptoTaskConfig.getText())
            ));
        });

        webTasks.forEach(webTaskConfig -> this.add(new Task(
                webTaskConfig.getName(),
                webTaskConfig.getLevel(),
                TaskType.WEB,
                webTaskConfig.getFlags().stream()
                        .map(Flag::new)
                        .collect(Collectors.toList()),
                new TaskWebContent(webTaskConfig.getUrl())
        )));
    }

    public void add(Task task)
    {
        datastore.save(task);
    }

    public void clean()
    {
        datastore.getCollection(Task.class).drop();
    }

    //todo: refactor?
    public Map<List<String>, Integer> getUserFlagsHashes(String username)
    {
        return this.getAll().stream()
                .collect(Collectors.toMap(
                        task -> task.getFlags().stream()
                                .map(flag -> calculateHashValue(username, flag.getValue()))
                                .collect(Collectors.toList()),
                        Task::getLevel
                ));
    }

    public String calculateHashValue(String username, String flagValue)
    { //todo
        String combinedStrings = salt + username + flagValue; //todo
        MessageDigest md5 = null;//todo: discuss
        try {
            md5 = MessageDigest.getInstance(applicationConfiguration.getFlagHashMethod());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        String encodedHash = Hex.encodeHexString(md5.digest(combinedStrings.getBytes()));
        return encodedHash;
    }

    private void acceptSolution(String username, Integer taskLevel)
    {
        User user = usersRepository.getUserByName(username);
        Optional<Team> team = teamsRepository.getTeamByUser(user);
        Optional<Task> task = getTaskByLevel(taskLevel);
        if (team.isPresent() && task.isPresent()) {
            solutionsRepository.add(new Solution(team.get(), task.get().getName()));
        }
    }

    private Optional<Task> getTaskByLevel(Integer level)
    {
        return getAll().stream()
                .filter(task -> task.getLevel() == level)
                .findFirst();
    }

    //todo: cleanup
    //todo: depracted?
    private void acceptSolution(String username, Task task)
    {
        User user = usersRepository.getUserByName(username);
        Optional<Team> team = teamsRepository.getTeamByUser(user);
        if (team.isPresent()) {
            solutionsRepository.add(new Solution(team.get(), task.getName()));
        }
    }

    private Optional<Integer> findTaskLevelByFlag(String username, String flagValue)
    {
        return getUserFlagsHashes(username).entrySet()
                .stream()
                .filter(flagsMapEntry -> flagsMapEntry.getKey().contains(flagValue))
                .map(Map.Entry::getValue)
                .findFirst();
    }

    public boolean checkFlag(String username, String flagValue)
    {
        Optional<Integer> taskLevel = findTaskLevelByFlag(username, flagValue);
        if (taskLevel.isPresent()) {
            acceptSolution(username, taskLevel.get());
            return true;
        }
        return false;
    }

    public Optional<List<String>> getUserTaskFlags(String username, Integer taskLevel)
    { //todo: task level, task id
        return getUserFlagsHashes(username).entrySet().stream()
                .filter(taskEntry -> taskEntry.getValue().equals(taskLevel))
                .map(listIntegerEntry -> {
                    Integer taskLevel1 = listIntegerEntry.getValue();
                    Task task = getTaskByLevel(taskLevel1).get();
                    List<String> flags = task.getFlags()
                            .stream()
                            .map(flag -> calculateHashValue(username, flag.getValue()))
                            .collect(Collectors.toList());
                    return flags;
                })
                .findFirst();
    }
}