const path = require("path");
const fs = require("fs");

function generateFromFile(filePath) {
    const reports = require(filePath);

    return generateFromBuildOutputs(reports);
}

function generateFromBuildOutputs(buildOutputs) {
    if (buildOutputs.length === 0) {
        console.error("Nothing appears to have been built");
        return [];
    }

    const parsedBuildOutputs = [];
    for (const build of buildOutputs) {
        const failed = [];
        for (const blueprintName in build.failed) {
            failed.push({
                blueprintName,
                parsedErrors: build.parsedErrors[blueprintName] ?? parseErrorsFromOutput(build.failed[blueprintName]),
            });
        }
        if (failed.length > 0) {
            parsedBuildOutputs.push({ name: build.name, failed });
        }
    }

    const metadata = {};

    for (const buildOutput of parsedBuildOutputs) {
        const clientName = buildOutput.name;

        for (const { blueprintName, parsedErrors } of buildOutput.failed) {
            for (const error of parsedErrors) {
                const key = `${error.message}`;
                if (!metadata[key]) {
                    metadata[key] = [];
                }
                metadata[key].push({
                    clientName: clientName,
                    blueprintName: blueprintName,
                    location: error.location,
                    fileName: error.fileName,
                    codeSnippet: error.codeSnippet,
                });
            }
        }
    }

    const sortedMetadata = {};
    for (const key of Object.keys(metadata).sort()) {
        sortedMetadata[key] = metadata[key];
    }

    return sortedMetadata;
}

function parseErrorsFromOutput(outputFromBuild) {
    const colorEscapeCodesRegex = /\u001b\[[0-9]+m/gi;
    const lines = outputFromBuild.split("\n");
    const errors = [];
    let foundError = false;
    for (let x = 0; x < lines.length; x++) {
        let errorLocation = "";
        while (lines[x].replace(colorEscapeCodesRegex, "").trim().startsWith("ERROR in ")) {
            errorLocation = lines[x];
            x++;
            foundError = true;
        }

        if (foundError) {
            let nextError = "";
            while (lines[x] !== "") {
                nextError += lines[x] + "\n";
                x++;
            }

            nextError = nextError.replace(colorEscapeCodesRegex, "").trim();

            const errorPath = errorLocation.replace(colorEscapeCodesRegex, "").trim().replace("ERROR in ", "");
            const lineAndColumnRegex = /\((?<lineNumber>[0-9]+),[0-9]+\)\:/i;
            const fileName = path.basename(errorPath).replace(lineAndColumnRegex, "");
            const filePath = errorPath.replace(lineAndColumnRegex, "");
            const lineNumber = errorPath.match(lineAndColumnRegex)?.groups?.lineNumber;
            const codeSnippet = getCodeSnippet(filePath, lineNumber);

            errors[errors.length] = {
                message: nextError,
                location: errorPath,
                fileName: fileName,
                codeSnippet: codeSnippet,
            };
            foundError = false;
        }
    }

    return errors;
}

function getCodeSnippet(filePath, lineNumber) {
    const codeSnippet = [];
    try {
        const file = fs.readFileSync(filePath, { encoding: "utf-8" })?.split("\n") ?? [];
        const targetLine = parseInt(lineNumber);
        for (let idx = targetLine - 2; idx <= targetLine + 2; idx++) {
            const value = file[idx - 1];
            if (value && value.trim()) {
                codeSnippet.push(value.trim());
            }
        }
    } catch (error) {}
    return codeSnippet.join("\n\n");
}

function parseErrorsFromStats() {
    const errorsFromStats = [];
    errorsFromStats.push(...getErrorsFromStatsFile("../dist/stats.json"));
    errorsFromStats.push(...getErrorsFromStatsFile("../wwwroot/dist/stats.json"));
    errorsFromStats.push(...getErrorsFromStatsFile("../wwwroot/dist/stats-shell.json"));
    var errors = [];

    for (const error of errorsFromStats) {
        if (shouldSkipErrorReporting(error)) {
            continue;
        }
        const typeScriptError = tryParseJson(error.message);
        if (typeScriptError) {
            errors.push({
                message: typeScriptError.message,
                location: typeScriptError.file,
                fileName: path.basename(typeScriptError.file),
                codeSnippet: typeScriptError.snippet,
            });
        } else {
            errors.push({
                message: formatWebpackMessage(error),
                location: error.moduleName,
                fileName: error.moduleName ? path.basename(error.moduleName) : "",
                codeSnippet: formatWebpackCodeSnippet(error),
            });
        }
    }

    return errors;
}

function getErrorsFromStatsFile(statsFilePath) {
    if (fs.existsSync(statsFilePath)) {
        const serverStats = JSON.parse(fs.readFileSync(statsFilePath, "utf-8"));
        return serverStats?.errors ?? [];
    }
    return [];
}

function shouldSkipErrorReporting(error) {
    if (!error) {
        return true;
    }

    const blueprintBuilderReplacementErrorRegex = /File .+ should be moved to .+, so that it is properly overridden\./i;
    if (blueprintBuilderReplacementErrorRegex.test(error?.message)) {
        return true;
    }

    return false;
}

function formatWebpackMessage(webpackError) {
    const moduleNotFoundRegex = /(?<message>Module not found: Error: Can't resolve '.+') in '.+'/i;
    if (moduleNotFoundRegex.test(webpackError.message)) {
        const message = webpackError.message.match(moduleNotFoundRegex)?.groups?.message;
        if (message) {
            return message;
        }
    }

    return webpackError.message;
}

function formatWebpackCodeSnippet(webpackError) {
    // The line numbers returned in the webpack error don't seem to match actual numbers in the referenced file.
    // Removing this logic for now. Look in the git history to see previous code
    return "Webpack Error";
}

function tryParseJson(string) {
    try {
        var o = JSON.parse(string);
        if (o && typeof o === "object") {
            return o;
        }
    } catch (e) {}

    return false;
}

module.exports = { parseErrorsFromOutput, generateFromBuildOutputs, generateFromFile, parseErrorsFromStats };
