package com.telephoners.krakyournet.ctf.repositories;

import com.telephoners.krakyournet.ctf.core.ApplicationConfiguration;
import com.telephoners.krakyournet.ctf.helpers.DBObjectUtils;
import com.telephoners.krakyournet.ctf.objects.Flag;
import com.telephoners.krakyournet.ctf.objects.Solution;
import com.telephoners.krakyournet.ctf.objects.Team;
import com.telephoners.krakyournet.ctf.objects.User;
import com.telephoners.krakyournet.ctf.objects.tasks.Task;
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.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

@Singleton
public class TasksRepository implements Repository
{

    private ApplicationConfiguration applicationConfiguration;
    private Datastore datastore;
    private TeamsRepository teamsRepository;
    private SolutionsRepository solutionsRepository;
    private String salt = "SECURE_SALT"; //todo: move to configuration!

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

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

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

    public List<Task> getAllPublic()
    {
        return datastore.createQuery(Task.class)
                .retrievedFields(true, DBObjectUtils.getPublicFields(Task.class))
                .asList();
    }

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

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

    //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
                ));
    }

    private Optional<Task> getTaskFlagByHashValue(User user, String userHash)
    {
        String username = user.getName();
        //todo: inline
        //todo: collapse lambdas
        List<Task> tasks = this.getAll();
        Optional<Task> task = tasks.stream()
                .filter(new Predicate<Task>()
                {
                    @Override
                    public boolean test(Task task)
                    {
                        return task.getFlags()
                                .stream()
                                .map(new Function<Flag, String>()
                                {
                                    @Override
                                    public String apply(Flag flag)
                                    {
                                        return calculateHashValue(username, flag.getValue());
                                    }
                                })
                                .filter(new Predicate<String>()
                                {
                                    @Override
                                    public boolean test(String flagValue)
                                    {
                                        return flagValue.equals(userHash);
                                    }
                                })
                                .findFirst()
                                .isPresent();
                    }
                })
                .findFirst();
        return task;
    }

    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;
    }

    public boolean checkFlag(User user, String hashValue)
    {

        Optional<Task> task = getTaskFlagByHashValue(user, hashValue);
//        Optional<Task> task = getByUserFlag(username, hashValue);
        Optional<Team> team = teamsRepository.getTeamByUser(user);
        if (task.isPresent() && team.isPresent()) {
            solutionsRepository.add(new Solution(team.get(), task.get(), null)); //todo
            return true;
        }
        return false;
    }

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