package de.hftstuttgart.rest.v1.task;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Volume;
import de.hftstuttgart.models.LegacyMoodleResult;
import de.hftstuttgart.models.ModocotResultSummary;
import de.hftstuttgart.rest.v1.unittest.UnitTestUpload;
import de.hftstuttgart.utils.DockerUtil;
import de.hftstuttgart.utils.FileUtil;
import de.hftstuttgart.utils.JGitUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.annotation.MultipartConfig;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Rest controller for everything related to the TASK files
 */
@RestController
@RequestMapping("/v1/task")
@MultipartConfig
public class TaskUpload {
    private static final Logger LOG = LogManager.getLogger(TaskUpload.class);

    private final JGitUtil jGitUtil;
    private final DockerUtil dockerUtil;
    private final String assignmentBasePath;
    private final Path testTmpPathHost;
    private final Path testTmpPathModocot;


    public TaskUpload(
        Environment env,
        JGitUtil jGitUtil,
        DockerUtil dockerUtil
    ) {
        this.jGitUtil = jGitUtil;
        this.dockerUtil = dockerUtil;

        // set base path for assignments to be stored
        Path p = Paths.get(
            env.getProperty("modocot.dir"),
            env.getProperty("modocot.dir.test.folder.name"));
        this.assignmentBasePath = p.toAbsolutePath().toString();

        // set path of temporary directory on host and inside our container
        this.testTmpPathHost = Paths.get(env.getProperty("host.tests.tmp.dir"));
        this.testTmpPathModocot = Paths.get(env.getProperty("modocot.tests.tmp.dir"));
    }

    @RequestMapping(method = RequestMethod.POST)
    public LegacyMoodleResult uploadAndTestFile(@RequestParam("taskFile") MultipartFile taskFileRef,
                                    @RequestParam("assignmentId") String assignmentId
    ) throws IOException, InterruptedException {
        LOG.info("submission for testing received");

        LOG.debug("creating new temporary directory");
        Path workDirectory = Files.createTempDirectory(testTmpPathModocot, "modocot");
        LOG.debug(String.format("working dir for test is: %s", workDirectory.toAbsolutePath().toString()));

        // define paths for the test, the submission and where the result is to be expected afterwards
        Path testPath   = Paths.get(workDirectory.toAbsolutePath().toString(), "test");
        Path srcPath    = Paths.get(workDirectory.toAbsolutePath().toString(), "src");
        Path resultPath = Paths.get(workDirectory.toAbsolutePath().toString(), "result");

        // clone stored test to tmpdir
        LOG.debug("copying pre-downloaded unitttest repo");
        FileUtil.copyFolder(
            Paths.get(
                assignmentBasePath,
                assignmentId
            ),
            testPath
        );

        LOG.debug("copy test config");
        Files.copy(
            Paths.get(
                assignmentBasePath,
                assignmentId + ".txt"
            ),
            Paths.get(
                workDirectory.toAbsolutePath().toString(),
                "config.txt"
            )
        );

        // clone student code to tmpdir
        Pattern pattern = Pattern.compile(UnitTestUpload.modocotDueConfigRegex);
        Matcher config = null;

        LOG.debug("reading task file");
        // open received file in a try-with
        try (BufferedReader br = new BufferedReader(
            new InputStreamReader(
                taskFileRef.getInputStream()))) {
            String line;

            // as long as we haven't found a configuration and have lines left, search
            while (config == null && (line = br.readLine()) != null) {
                Matcher matcher = pattern.matcher(line);
                if (matcher.matches()) {
                    LOG.debug(String.format("found modocot line: %s", line));
                    config = matcher;
                }
            }
        } catch (IOException e) {
            LOG.error("Error while reading repo config", e);
        }
        finally {
            if (config == null) {
                throw new RuntimeException("couldn't find repo config for unittest clone");
            }
        }

        LOG.debug("calling repo clone");
        jGitUtil.cloneRepository(config, srcPath.toAbsolutePath().toString());

        Files.createDirectory(resultPath);

        LOG.info("starting unittest");
        pattern = Pattern.compile(UnitTestUpload.modocotTestConfigRegex);
        config = null;

        LOG.debug("reading test config");
        // open test config and read it in again, important to know with which docker image the test should be run
        try (BufferedReader br = new BufferedReader(
            new InputStreamReader(
                new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile())))) {
            String line;
            while (config == null && (line = br.readLine()) != null) {
                Matcher matcher = pattern.matcher(line);
                if (matcher.matches()) {
                    LOG.debug(String.format("found modocot line: %s", line));
                    config = matcher;
                }
            }
        } catch (IOException e) {
            LOG.error("Error while reading test config", e);
        }
        finally {
            if (config == null) {
                throw new RuntimeException("couldn't find test config for unittest");
            }
        }

        // define the paths to mount as Binds from Host to the test-container
        Path testPathHost = Paths.get(
            testTmpPathHost.toAbsolutePath().toString(),
            workDirectory.getName(workDirectory.getNameCount()-1).toString(),
            testPath.getName(testPath.getNameCount()-1).toString()
        );
        Path srcPathHost = Paths.get(
            testTmpPathHost.toAbsolutePath().toString(),
            workDirectory.getName(workDirectory.getNameCount()-1).toString(),
            srcPath.getName(srcPath.getNameCount()-1).toString()
        );
        Path resultPathHost = Paths.get(
            testTmpPathHost.toAbsolutePath().toString(),
            workDirectory.getName(workDirectory.getNameCount()-1).toString(),
            resultPath.getName(resultPath.getNameCount()-1).toString()
        );

        // start test-container with professor given image and bind mounts for test, submission and result
        dockerUtil.runContainer(
            config.group(4),
            new Bind(testPathHost.toAbsolutePath().toString(), new Volume("/modocot/test")),
            new Bind(srcPathHost.toAbsolutePath().toString(), new Volume("/modocot/src")),
            new Bind(resultPathHost.toAbsolutePath().toString(), new Volume("/modocot/result"))
        );

        // define expected result file
        File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile();

        // check if result file is there
        if (!resultFile.exists() || !resultFile.isFile()) {
            LOG.error(String.format("couln't find result file in %s", resultFile.getAbsolutePath()));
            throw new RuntimeException("no resultfile found");
        }

        LOG.debug("parse results json");
        ObjectMapper objectMapper = new ObjectMapper();
        ModocotResultSummary resultSummary = objectMapper.readValue(
            resultFile.toURI().toURL(),
            ModocotResultSummary.class);

        // convert to moddle plugin readable format and return to moodle
        LOG.debug("convert to moodle understandable format");
        LegacyMoodleResult moodleResult = LegacyMoodleResult.convertToModdleResult(resultSummary);

        LOG.info("submission tested successfully");
        return moodleResult;
    }
}
