package com.telephoners.krakyournet.ctf.repositories;

import com.google.inject.name.Named;
import com.telephoners.krakyournet.ctf.beans.Flag;
import com.telephoners.krakyournet.ctf.beans.Solution;
import com.telephoners.krakyournet.ctf.beans.Team;
import com.telephoners.krakyournet.ctf.beans.User;
import com.telephoners.krakyournet.ctf.beans.tasks.Task;
import com.telephoners.krakyournet.ctf.core.ApplicationConfiguration;
import com.telephoners.krakyournet.ctf.helpers.DBObjectUtils;
import javafx.util.Pair;
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.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Singleton
public class TasksRepository extends Repository<Task>
{
    private ApplicationConfiguration applicationConfiguration;
    private Datastore datastore;
    private TeamsRepository teamsRepository;
    private SolutionsRepository solutionsRepository;
    private MessageDigest messageDigest;

    @Inject
    public TasksRepository(ApplicationConfiguration applicationConfiguration, Datastore datastore,
                           TeamsRepository teamsRepository, SolutionsRepository solutionsRepository,
                           final @Named("messageDigest") MessageDigest messageDigest)
    {
        super(datastore);
        this.applicationConfiguration = applicationConfiguration;
        this.datastore = datastore;
        this.teamsRepository = teamsRepository;
        this.solutionsRepository = solutionsRepository;
        this.messageDigest = messageDigest;
    }

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

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

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

    private Optional<Pair<Task, Flag>> getTaskFlagPairByHashValue(User user, String userHash, int taskLevel)
    {
        String username = user.getName();
        Optional<Flag> matchedFlag = getByLevel(taskLevel).getFlags().stream()
                .filter(flag -> calculateHashValue(username, flag.getValue()).equals(userHash))
                .findFirst();
        if (matchedFlag.isPresent()) {
            return Optional.of(new Pair<>(getByLevel(taskLevel), matchedFlag.get()));
        }
        return Optional.empty();
    }

    public String calculateHashValue(String username, String flagValue)
    {
        String combinedStrings = applicationConfiguration.getSalt() + username + flagValue;
        return Hex.encodeHexString(messageDigest.digest(combinedStrings.getBytes()));
    }

    public boolean checkHash(User user, String hashValue, int taskLevel)
    {
        //todo: refactor
        Optional<Pair<Task, Flag>> taskFlagPairOptional = getTaskFlagPairByHashValue(user, hashValue, taskLevel);
        if (!taskFlagPairOptional.isPresent()) {
            return false;
        }

        Pair<Task, Flag> taskFlagPair = taskFlagPairOptional.get();
        Task task = taskFlagPair.getKey();
        Flag flag = taskFlagPair.getValue();
        Optional<Team> team = teamsRepository.getTeamByUser(user);
        if (team.isPresent()) {
            //todo: combine ifs
            Solution solution = new Solution(team.get(), task, flag, hashValue);
            if (!solutionsRepository.isAlreadySubmittedSolution(solution)) {
                solutionsRepository.add(solution);
                return true;
            }
        }
        return false;
    }

    //todo: should it be here?
    public List<Integer> getCompletedTasks(Team team)
    {
        Map<Integer, List<String>> teamSolutions = solutionsRepository.getTeamSolutions(team);
        return getAll().stream()
                .filter(task -> {
                    int numberOfFlags = task.getFlags().size();
                    List<String> teamTaskSolutions = teamSolutions.get(task.getLevel());
                    return teamTaskSolutions != null && teamTaskSolutions.size() == numberOfFlags;
                })
                .map(Task::getLevel)
                .collect(Collectors.toList());
    }
}