import fs from "fs";
import sanitize from "sanitize-filename";
import sql from "./sql.js";
import decryptService from "./decrypt.js";
import dataKeyService from "./data_key.js";
import extensionService from "./extension.js";

function dumpDocument(documentPath: string, targetPath: string, options: { password: any; includeDeleted: any }) {
    const stats = {
        succeeded: 0,
        failed: 0,
        protected: 0,
        deleted: 0
    };

    validatePaths(documentPath, targetPath);

    sql.openDatabase(documentPath);

    const dataKey = dataKeyService.getDataKey(options.password);

    const existingPaths: Record<string, any> = {};
    const noteIdToPath: Record<string, any> = {};

    dumpNote(targetPath, "root");

    printDumpResults(stats, options);

    function dumpNote(targetPath: any, noteId: any) {
        console.log(`Reading note '${noteId}'`);

        let childTargetPath, noteRow, fileNameWithPath;

        try {
            noteRow = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);

            if (noteRow.isDeleted) {
                stats.deleted++;

                if (!options.includeDeleted) {
                    console.log(`Note '${noteId}' is deleted and --include-deleted option is not used, skipping.`);

                    return;
                }
            }

            if (noteRow.isProtected) {
                stats.protected++;

                noteRow.title = decryptService.decryptString(dataKey, noteRow.title);
            }

            let safeTitle = sanitize(noteRow.title);

            if (safeTitle.length > 20) {
                safeTitle = safeTitle.substring(0, 20);
            }

            childTargetPath = targetPath + "/" + safeTitle;

            for (let i = 1; i < 100000 && childTargetPath in existingPaths; i++) {
                childTargetPath = targetPath + "/" + safeTitle + "_" + i;
            }

            existingPaths[childTargetPath] = true;

            if (noteRow.noteId in noteIdToPath) {
                const message = `Note '${noteId}' has been already dumped to ${noteIdToPath[noteRow.noteId]}`;

                console.log(message);

                fs.writeFileSync(childTargetPath, message);

                return;
            }

            let { content } = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]);

            if (content !== null && noteRow.isProtected && dataKey) {
                content = decryptService.decrypt(dataKey, content);
            }

            if (isContentEmpty(content)) {
                console.log(`Note '${noteId}' is empty, skipping.`);
            } else {
                fileNameWithPath = extensionService.getFileName(noteRow, childTargetPath, safeTitle);

                fs.writeFileSync(fileNameWithPath, content);

                stats.succeeded++;

                console.log(`Dumped note '${noteId}' into ${fileNameWithPath} successfully.`);
            }

            noteIdToPath[noteId] = childTargetPath;
        } catch (e: any) {
            console.error(`DUMPERROR: Writing '${noteId}' failed with error '${e.message}':\n${e.stack}`);

            stats.failed++;
        }

        const childNoteIds = sql.getColumn("SELECT noteId FROM branches WHERE parentNoteId = ?", [noteId]);

        if (childNoteIds.length > 0) {
            if (childTargetPath === fileNameWithPath) {
                childTargetPath += "_dir";
            }

            try {
                fs.mkdirSync(childTargetPath as string, { recursive: true });
            } catch (e: any) {
                console.error(`DUMPERROR: Creating directory ${childTargetPath} failed with error '${e.message}'`);
            }

            for (const childNoteId of childNoteIds) {
                dumpNote(childTargetPath, childNoteId);
            }
        }
    }
}

function printDumpResults(stats: any, options: any) {
    console.log("\n----------------------- STATS -----------------------");
    console.log("Successfully dumpted notes:   ", stats.succeeded.toString().padStart(5, " "));
    console.log("Protected notes:              ", stats.protected.toString().padStart(5, " "), options.password ? "" : "(skipped)");
    console.log("Failed notes:                 ", stats.failed.toString().padStart(5, " "));
    console.log("Deleted notes:                ", stats.deleted.toString().padStart(5, " "), options.includeDeleted ? "(dumped)" : "(at least, skipped)");
    console.log("-----------------------------------------------------");

    if (!options.password && stats.protected > 0) {
        console.log("\nWARNING: protected notes are present in the document but no password has been provided. Protected notes have not been dumped.");
    }
}

function isContentEmpty(content: any) {
    if (!content) {
        return true;
    }

    if (typeof content === "string") {
        return !content.trim() || content.trim() === "<p></p>";
    } else if (Buffer.isBuffer(content)) {
        return content.length === 0;
    } else {
        return false;
    }
}

function validatePaths(documentPath: string, targetPath: string) {
    if (!fs.existsSync(documentPath)) {
        console.error(`Path to document '${documentPath}' has not been found. Run with --help to see usage.`);
        process.exit(1);
    }

    if (!fs.existsSync(targetPath)) {
        const ret = fs.mkdirSync(targetPath, { recursive: true });

        if (!ret) {
            console.error(`Target path '${targetPath}' could not be created. Run with --help to see usage.`);
            process.exit(1);
        }
    }
}

export default {
    dumpDocument
};
