// ATTENTION : This line MUST be the first of this file
import './init.js';
import ClientsSharedConfig                  from './sharedByClients/config/ClientsSharedConfig.js';
import Styles                               from './types/Style.js';
import RecursiveFilesStats                  from './shared/helpers/recursiveFilesStats/RecursiveFilesStats.js';
import Toolbox                              from './shared/helpers/Toolbox.js';
import ExerciseCheckerError                 from './shared/types/Dojo/ExerciseCheckerError.js';
import fs                                   from 'fs-extra';
import HttpManager                          from './managers/HttpManager.js';
import DojoBackendManager                   from './managers/DojoBackendManager.js';
import Config                               from './config/Config.js';
import ArchiveHelper                        from './shared/helpers/ArchiveHelper.js';
import ExerciseDockerCompose                from './sharedByClients/helpers/Dojo/ExerciseDockerCompose.js';
import ExerciseResultsSanitizerAndValidator from './sharedByClients/helpers/Dojo/ExerciseResultsSanitizerAndValidator.js';
import ExerciseAssignment                   from './sharedByClients/models/ExerciseAssignment.js';
import ClientsSharedExerciseHelper          from './sharedByClients/helpers/Dojo/ClientsSharedExerciseHelper.js';
import Icon                                 from './shared/types/Icon.js';
import path                                 from 'node:path';
import SharedAssignmentHelper               from './shared/helpers/Dojo/SharedAssignmentHelper';


let exerciseAssignment: ExerciseAssignment | undefined;
let exerciseDockerCompose: ExerciseDockerCompose;
let exerciseResultsValidation: ExerciseResultsSanitizerAndValidator;

let haveResultsVolume: boolean;

/**
 * Step 1:
 * - Read the dojo assignment file from the assignment repository
 * - Download immutables files (maybe throw or show an error if the files have been modified ?)
 */
async function downloadImmutablesFiles() {
    console.log(Styles.INFO(`${ Icon.INFO }️ Checking the exercise's assignment and his immutable files`));
    exerciseAssignment = await DojoBackendManager.getExerciseAssignment();
    if ( !exerciseAssignment ) {
        console.error(Styles.ERROR(`${ Icon.ERROR } Error while getting the exercise's assignment`));
        process.exit(ExerciseCheckerError.EXERCISE_ASSIGNMENT_GET_ERROR);
    }

    exerciseAssignment.immutable.forEach(immutableFile => {
        if ( typeof immutableFile.content === 'string' ) {
            const filePath = path.join(Config.folders.project, immutableFile.file_path);
            fs.mkdirSync(path.dirname(filePath), { recursive: true });
            fs.writeFileSync(filePath, immutableFile.content, { encoding: 'base64' });
        }
    });

    haveResultsVolume = exerciseAssignment.assignmentFile.result.volume !== undefined;
}


/**
 * Step 2:
 * - Get override of docker-compose file (for override the volume by a bind mount to the results folder shared between dind and the host)
 * - Run docker-compose file
 * - Get logs from linked services
 */
async function runDockerCompose() {
    let composeFileOverride: string[] = [];
    const composeOverridePath: string = path.join(Config.folders.project, 'docker-compose-override.yml');
    if ( haveResultsVolume ) {
        const composeOverride = fs.readFileSync(path.join(__dirname, '../assets/docker-compose-override.yml'), 'utf8').replace('{{VOLUME_NAME}}', exerciseAssignment!.assignmentFile.result.volume!).replace('{{MOUNT_PATH}}', Config.folders.resultsExercise);
        fs.writeFileSync(composeOverridePath, composeOverride);

        composeFileOverride = [ composeOverridePath ];
    }

    exerciseDockerCompose = new ExerciseDockerCompose(ClientsSharedConfig.dockerCompose.projectName, exerciseAssignment!.assignmentFile, Config.folders.project, composeFileOverride);

    try {
        await new Promise<void>((resolve, reject) => {
            exerciseDockerCompose.events.on('step', (_name: string, message: string) => {
                console.log(Styles.INFO(`${ Icon.INFO } ${ message }`));
            });

            exerciseDockerCompose.events.on('endStep', (_stepName: string, message: string, error: boolean) => {
                if ( error ) {
                    console.error(Styles.ERROR(`${ Icon.ERROR } ${ message }`));
                }
            });

            exerciseDockerCompose.events.on('finished', (success: boolean) => {
                success ? resolve() : reject();
            });

            exerciseDockerCompose.run();
        });
    } catch ( error ) { /* empty */ }

    fs.rmSync(composeOverridePath, { force: true });
    fs.writeFileSync(path.join(Config.folders.resultsDojo, 'dockerComposeLogs.txt'), exerciseDockerCompose.allLogs);

    if ( !exerciseDockerCompose.success ) {
        console.error(Styles.ERROR(`${ Icon.ERROR } Execution logs are available in artifacts`));
        process.exit(exerciseDockerCompose.exitCode);
    }
}


/**
 * Step 3:
 * - Check content requirements and content size
 */
async function checkExecutionContent() {
    exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(Config.folders.resultsDojo, Config.folders.resultsExercise, exerciseDockerCompose.exitCode);

    try {
        await new Promise<void>(resolve => {
            exerciseResultsValidation.events.on('step', (_name: string, message: string) => {
                console.log(Styles.INFO(`${ Icon.INFO } ${ message }`));
            });

            exerciseResultsValidation.events.on('endStep', (_stepName: string, message: string, error: boolean) => {
                if ( error ) {
                    console.error(Styles.ERROR(`${ Icon.ERROR } ${ message }`));
                }
            });

            exerciseResultsValidation.events.on('finished', (success: boolean, exitCode: number) => {
                if ( !success ) {
                    process.exit(exitCode);
                }

                resolve();
            });

            exerciseResultsValidation.run();
        });
    } catch ( error ) { /* empty */ }
}

/**
 * Step 4:
 * - Upload results
 */
async function uploadResults() {
    try {
        console.log(Styles.INFO(`${ Icon.INFO } Uploading results to the dojo server`));
        const commit: Record<string, string> = {};
        Toolbox.getKeysWithPrefix(process.env, 'CI_COMMIT_').forEach(key => {
            commit[Toolbox.snakeToCamel(key.replace('CI_COMMIT_', ''))] = process.env[key] as string;
        });

        const files = await RecursiveFilesStats.explore(Config.folders.resultsVolume, {
            replacePathByRelativeOne: true,
            liteStats               : true
        });

        await DojoBackendManager.sendResults(exerciseDockerCompose.exitCode, commit, exerciseResultsValidation.exerciseResults, files, await ArchiveHelper.getBase64(Config.folders.resultsVolume));
    } catch ( error ) {
        console.error(Styles.ERROR(`${ Icon.ERROR } Error while uploading the results`));
        console.error(JSON.stringify(error));
        process.exit(ExerciseCheckerError.UPLOAD);
    }
}


/**
 * Step 5:
 * - Display results
 * - Exit with container exit code
 */
async function displayResults() {
    ClientsSharedExerciseHelper.displayExecutionResults(exerciseResultsValidation.exerciseResults, exerciseDockerCompose.exitCode, Styles, `\n\n${ Icon.INFO }️ More detailed logs and resources may be available in artifacts`);

    process.exit(exerciseDockerCompose.exitCode);
}


(async () => {
    await Config.init();

    SharedAssignmentHelper.init(Config.gitlabManager);

    HttpManager.registerAxiosInterceptor();

    console.log(Styles.APP_NAME(`${ Config.appName } (version {{VERSION}})`));

    await downloadImmutablesFiles();
    await runDockerCompose();
    await checkExecutionContent();
    await uploadResults();
    await displayResults();
})();