const CoreClass = require("../core.js");

const async = require("async");

class ExampleModule extends CoreClass {
    constructor() {
        super("playlists");
    }

    initialize() {
        return new Promise(async (resolve, reject) => {
            this.setStage(1);

            this.cache = this.moduleManager.modules["cache"];
            this.db = this.moduleManager.modules["db"];
            this.utils = this.moduleManager.modules["utils"];

            const playlistModel = await this.db.runJob("GET_MODEL", {
                modelName: "playlist",
            });

            const playlistSchema = await this.cache.runJob("GET_SCHEMA", {
                schemaName: "playlist",
            });

            this.setStage(2);

            async.waterfall(
                [
                    (next) => {
                        this.setStage(3);
                        this.cache
                            .runJob("HGETALL", { table: "playlists" })
                            .then((playlists) => {
                                next(null, playlists);
                            })
                            .catch(next);
                    },

                    (playlists, next) => {
                        this.setStage(4);
                        if (!playlists) return next();
                        let playlistIds = Object.keys(playlists);
                        async.each(
                            playlistIds,
                            (playlistId, next) => {
                                playlistModel.findOne(
                                    { _id: playlistId },
                                    (err, playlist) => {
                                        if (err) next(err);
                                        else if (!playlist) {
                                            this.cache
                                                .runJob("HDEL", {
                                                    table: "playlists",
                                                    key: playlistId,
                                                })
                                                .then(() => {
                                                    next();
                                                })
                                                .catch(next);
                                        } else next();
                                    }
                                );
                            },
                            next
                        );
                    },

                    (next) => {
                        this.setStage(5);
                        playlistModel.find({}, next);
                    },

                    (playlists, next) => {
                        this.setStage(6);
                        async.each(
                            playlists,
                            (playlist, next) => {
                                this.cache
                                    .runJob("HSET", {
                                        table: "playlists",
                                        key: playlist._id,
                                        value: playlistSchema(playlist),
                                    })
                                    .then(() => {
                                        next();
                                    })
                                    .catch(next);
                            },
                            next
                        );
                    },
                ],
                async (err) => {
                    if (err) {
                        err = await this.utils.runJob("GET_ERROR", {
                            error: err,
                        });
                        reject(new Error(err));
                    } else {
                        resolve();
                    }
                }
            );
        });
    }

    /**
     * Gets a playlist by id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache
     *
     * @param {String} playlistId - the id of the playlist we are trying to get
     * @param {Function} cb - gets called once we're done initializing
     */
    GET_PLAYLIST(payload) {
        //playlistId, cb
        return new Promise(async (resolve, reject) => {
            const playlistModel = await this.db.runJob("GET_MODEL", {
                modelName: "playlist",
            });

            async.waterfall(
                [
                    (next) => {
                        this.cache
                            .runJob("HGETALL", { table: "playlists" })
                            .then((playlists) => {
                                next(null, playlists);
                            })
                            .catch(next);
                    },

                    (playlists, next) => {
                        if (!playlists) return next();
                        let playlistIds = Object.keys(playlists);
                        async.each(
                            playlistIds,
                            (playlistId, next) => {
                                playlistModel.findOne(
                                    { _id: playlistId },
                                    (err, playlist) => {
                                        if (err) next(err);
                                        else if (!playlist) {
                                            this.cache
                                                .runJob("HDEL", {
                                                    table: "playlists",
                                                    key: playlistId,
                                                })
                                                .then(() => {
                                                    next();
                                                })
                                                .catch(next);
                                        } else next();
                                    }
                                );
                            },
                            next
                        );
                    },

                    (next) => {
                        this.cache
                            .runJob("HGET", {
                                table: "playlists",
                                key: payload.playlistId,
                            })
                            .then((playlist) => {
                                next(null, playlist);
                            })
                            .catch(next);
                    },

                    (playlist, next) => {
                        if (playlist) return next(true, playlist);
                        playlistModel.findOne(
                            { _id: payload.playlistId },
                            next
                        );
                    },

                    (playlist, next) => {
                        if (playlist) {
                            this.cache
                                .runJob("HSET", {
                                    table: "playlists",
                                    key: payload.playlistId,
                                    value: playlist,
                                })
                                .then((playlist) => {
                                    next(null, playlist);
                                })
                                .catch(next);
                        } else next("Playlist not found");
                    },
                ],
                (err, playlist) => {
                    if (err && err !== true) return reject(new Error(err));
                    resolve(playlist);
                }
            );
        });
    }

    /**
     * Gets a playlist from id from Mongo and updates the cache with it
     *
     * @param {String} playlistId - the id of the playlist we are trying to update
     * @param {Function} cb - gets called when an error occurred or when the operation was successful
     */
    UPDATE_PLAYLIST(payload) {
        //playlistId, cb
        return new Promise(async (resolve, reject) => {
            const playlistModel = await this.db.runJob("GET_MODEL", {
                modelName: "playlist",
            });

            async.waterfall(
                [
                    (next) => {
                        playlistModel.findOne(
                            { _id: payload.playlistId },
                            next
                        );
                    },

                    (playlist, next) => {
                        if (!playlist) {
                            this.cache.runJob("HDEL", {
                                table: "playlists",
                                key: payload.playlistId,
                            });
                            return next("Playlist not found");
                        }
                        this.cache
                            .runJob("HSET", {
                                table: "playlists",
                                key: payload.playlistId,
                                value: playlist,
                            })
                            .then((playlist) => {
                                next(null, playlist);
                            })
                            .catch(next);
                    },
                ],
                (err, playlist) => {
                    if (err && err !== true) return reject(new Error(err));
                    resolve(playlist);
                }
            );
        });
    }

    /**
     * Deletes playlist from id from Mongo and cache
     *
     * @param {String} playlistId - the id of the playlist we are trying to delete
     * @param {Function} cb - gets called when an error occurred or when the operation was successful
     */
    DELETE_PLAYLIST(payload) {
        //playlistId, cb
        return new Promise(async (resolve, reject) => {
            const playlistModel = await this.db.runJob("GET_MODEL", {
                modelName: "playlist",
            });

            async.waterfall(
                [
                    (next) => {
                        playlistModel.deleteOne(
                            { _id: payload.playlistId },
                            next
                        );
                    },

                    (res, next) => {
                        this.cache
                            .runJob("HDEL", {
                                table: "playlists",
                                key: payload.playlistId,
                            })
                            .then(() => {
                                next();
                            })
                            .catch(next);
                    },
                ],
                (err) => {
                    if (err && err !== true) return reject(new Error(err));

                    resolve();
                }
            );
        });
    }
}

module.exports = new ExampleModule();