package de.hftstuttgart.dtabackend.utils;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
import de.hftstuttgart.dtabackend.models.Result;
import de.hftstuttgart.dtabackend.models.ResultSummary;
import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;

public class CompetencyAssessmentUtil {
    private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class);

    public static final String TEST_COMPETENCY_MANIFEST_FILE_NAME = "competency-tests.mft";
    public static final String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME = "exercise-tests.mft";

    /**
     * Retrieves the base directory where the test competency manifest is located
     * by traversing upwards until it finds the file "exercise-tests.mft".
     * 
     * @param startDir Starting directory path to begin the search
     * @return Path of the base directory if found; otherwise, null
     */
    public static Path getBaseDirectory(Path startDir) {
        Path currentDir = startDir;
        while (currentDir != null) {
            Path manifestPath = currentDir.resolve(EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
            if (Files.exists(manifestPath)) {
                return currentDir;
            }
            currentDir = currentDir.getParent(); 
        }
        LOG.warn("Base directory with " + EXERCISE_COMPETENCY_MANIFEST_FILE_NAME + " not found starting from " + startDir);
        return null;
    }

    /**
     * Reads and loads test competency profiles from the base directory.
     *
     * @param exercisePath Path to the starting exercise directory
     * @return List of TestCompetencyProfile instances loaded from the manifest
     */
    public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path exercisePath) {
        Path baseDir = getBaseDirectory(exercisePath);
        if (baseDir == null) {
            LOG.error("Unable to locate the base directory for reading test competency profiles.");
            return null;
        }
        return readTestCompetencyProfiles(baseDir, TEST_COMPETENCY_MANIFEST_FILE_NAME);
    }

    /**
     * Reads and loads exercise competency profiles from the specified exercise directory.
     *
     * @param exercisePath Path to the exercise directory
     * @return List of ExerciseCompetencyProfile instances loaded from the manifest
     */
    public static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath) {
        return readExerciseCompetencyProfiles(exercisePath, EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
    }

    private static List<TestCompetencyProfile> readTestCompetencyProfiles(Path baseDir, String fileName) {
        List<TestCompetencyProfile> testCompetencyProfiles = new ArrayList<>();
        Path manifestPath = baseDir.resolve(fileName);

        try (BufferedReader testCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
            String testEntry = testCompetencyManifest.readLine();
            while (testEntry != null) {
                String[] testEntryComponents = testEntry.split(ICompetencyProfile.COMPETENCY_SEPARATOR);
                TestCompetencyProfile currentProfile = new TestCompetencyProfile();
                currentProfile.testPackageName = testEntryComponents[0];
                currentProfile.testClassName = testEntryComponents[1];
                currentProfile.testName = testEntryComponents[2];
                for (int competencyIndex = 0; competencyIndex < ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
                    currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(testEntryComponents[competencyIndex + 3]);
                }
                testCompetencyProfiles.add(currentProfile);
                testEntry = testCompetencyManifest.readLine();
            }
            LOG.info("Loaded " + testCompetencyProfiles.size() + " test competency profiles from " + manifestPath);
        } catch (IOException e) {
            LOG.error("Error reading test competency manifest file at " + manifestPath, e);
        }

        return testCompetencyProfiles;
    }

    private static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) {
        List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>();
        Path manifestPath = exercisePath.resolve(fileName);

        try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
            String exerciseEntry = exerciseCompetencyManifest.readLine();
            while (exerciseEntry != null) {
                String[] exerciseEntryComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR);
                ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile();
                currentProfile.exerciseTopicName = exerciseEntryComponents[0];
                currentProfile.exerciseName = exerciseEntryComponents[1];
                currentProfile.exerciseURL = exerciseEntryComponents[2];
                for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
                    currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntryComponents[competencyIndex + 3]);
                }
                currentProfile.difficulty = Float.parseFloat(exerciseEntryComponents[19]);
                exerciseCompetencyProfiles.add(currentProfile);
                exerciseEntry = exerciseCompetencyManifest.readLine();
            }
            LOG.info("Loaded " + exerciseCompetencyProfiles.size() + " exercise competency profiles from " + manifestPath);
        } catch (IOException e) {
            LOG.error("Error reading exercise competency manifest file at " + manifestPath, e);
        }

        return exerciseCompetencyProfiles;
    }

    /**
     * Converts an array of floats into a semicolon-separated string.
     *
     * @param array Array of float values to pack
     * @return Semicolon-separated string of float values
     */
    public static String packFloats(float[] array) {
        return IntStream.range(0, array.length)
                .mapToObj(i -> String.valueOf(array[i]))
                .collect(Collectors.joining(";"));
    }

    /**
     * Sums the competency profiles across all test competency profiles.
     *
     * @param testCompetencyProfiles List of test competency profiles
     * @return An array representing the sum of competencies
     */
    public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
        float[] tcpTotalProfile = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
        for (TestCompetencyProfile currentProfile : testCompetencyProfiles) {
            tcpTotalProfile = ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
        }
        return tcpTotalProfile;
    }

    /**
     * Sums only the successful test competency profiles, based on the results summary.
     *
     * @param testCompetencyProfiles List of test competency profiles
     * @param resultSummary The result summary containing test results
     * @param success Indicates whether to sum successful (true) or unsuccessful (false) profiles
     * @return An array representing the sum of competencies for successful or unsuccessful profiles
     */
    public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) {
        float[] sumSuccessful = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
        for (Result currentResult : resultSummary.results) {
            boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
            if (isSuccess == success) {
                TestCompetencyProfile currentProfile = new TestCompetencyProfile();
                currentProfile.testPackageName = (currentResult.packageName != null) ? currentResult.packageName : "";
                currentProfile.testClassName = (currentResult.className != null) ? currentResult.className : "";
                currentProfile.testName = (currentResult.name != null) ? currentResult.name : "";
                int testIndex = testCompetencyProfiles.indexOf(currentProfile);
                if (testIndex != -1) {
                    sumSuccessful = ICompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments);
                }
            }
        }
        return sumSuccessful;
    }

    /**
     * Checks if all elements in the competency profile array are zero (indicating full success).
     *
     * @param competencyProfile Array of competency values
     * @return True if all values are zero; false otherwise
     */
    public static boolean isFullSuccess(float[] competencyProfile) {
        for (float value : competencyProfile) {
            if (value != 0.0f) {
                return false;
            }
        }
        return true;
    }
}