"use strict";

const config = require("config");
const async = require("async");
const request = require("request");

const hooks = require("./hooks");

const db = require("../db");
const utils = require("../utils");
const cache = require("../cache");
// const logger = moduleManager.modules["logger"];

cache.runJob("SUB", {
    channel: "queue.newSong",
    cb: async (songId) => {
        const queueSongModel = await db.runJob("GET_MODEL", {
            modelName: "queueSong",
        });
        queueSongModel.findOne({ _id: songId }, (err, song) => {
            utils.runJob("EMIT_TO_ROOM", {
                room: "admin.queue",
                args: ["event:admin.queueSong.added", song],
            });
        });
    },
});

cache.runJob("SUB", {
    channel: "queue.removedSong",
    cb: (songId) => {
        utils.runJob("EMIT_TO_ROOM", {
            room: "admin.queue",
            args: ["event:admin.queueSong.removed", songId],
        });
    },
});

cache.runJob("SUB", {
    channel: "queue.update",
    cb: async (songId) => {
        const queueSongModel = await db.runJob("GET_MODEL", {
            modelName: "queueSong",
        });
        queueSongModel.findOne({ _id: songId }, (err, song) => {
            utils.runJob("EMIT_TO_ROOM", {
                room: "admin.queue",
                args: ["event:admin.queueSong.updated", song],
            });
        });
    },
});

let lib = {
    /**
     * Returns the length of the queue songs list
     *
     * @param session
     * @param cb
     */
    length: hooks.adminRequired(async (session, cb) => {
        const queueSongModel = await db.runJob("GET_MODEL", {
            modelName: "queueSong",
        });
        async.waterfall(
            [
                (next) => {
                    queueSongModel.countDocuments({}, next);
                },
            ],
            async (err, count) => {
                if (err) {
                    err = await utils.runJob("GET_ERROR", { error: err });
                    console.log(
                        "ERROR",
                        "QUEUE_SONGS_LENGTH",
                        `Failed to get length from queue songs. "${err}"`
                    );
                    return cb({ status: "failure", message: err });
                }
                console.log(
                    "SUCCESS",
                    "QUEUE_SONGS_LENGTH",
                    `Got length from queue songs successfully.`
                );
                cb(count);
            }
        );
    }),

    /**
     * Gets a set of queue songs
     *
     * @param session
     * @param set - the set number to return
     * @param cb
     */
    getSet: hooks.adminRequired(async (session, set, cb) => {
        const queueSongModel = await db.runJob("GET_MODEL", {
            modelName: "queueSong",
        });
        async.waterfall(
            [
                (next) => {
                    queueSongModel
                        .find({})
                        .skip(15 * (set - 1))
                        .limit(15)
                        .exec(next);
                },
            ],
            async (err, songs) => {
                if (err) {
                    err = await utils.runJob("GET_ERROR", { error: err });
                    console.log(
                        "ERROR",
                        "QUEUE_SONGS_GET_SET",
                        `Failed to get set from queue songs. "${err}"`
                    );
                    return cb({ status: "failure", message: err });
                }
                console.log(
                    "SUCCESS",
                    "QUEUE_SONGS_GET_SET",
                    `Got set from queue songs successfully.`
                );
                cb(songs);
            }
        );
    }),

    /**
     * Updates a queuesong
     *
     * @param {Object} session - the session object automatically added by socket.io
     * @param {String} songId - the id of the queuesong that gets updated
     * @param {Object} updatedSong - the object of the updated queueSong
     * @param {Function} cb - gets called with the result
     */
    update: hooks.adminRequired(async (session, songId, updatedSong, cb) => {
        const queueSongModel = await db.runJob("GET_MODEL", {
            modelName: "queueSong",
        });
        async.waterfall(
            [
                (next) => {
                    queueSongModel.findOne({ _id: songId }, next);
                },

                (song, next) => {
                    if (!song) return next("Song not found");
                    let updated = false;
                    let $set = {};
                    for (let prop in updatedSong)
                        if (updatedSong[prop] !== song[prop])
                            $set[prop] = updatedSong[prop];
                    updated = true;
                    if (!updated) return next("No properties changed");
                    queueSongModel.updateOne(
                        { _id: songId },
                        { $set },
                        { runValidators: true },
                        next
                    );
                },
            ],
            async (err) => {
                if (err) {
                    err = await utils.runJob("GET_ERROR", { error: err });
                    console.log(
                        "ERROR",
                        "QUEUE_UPDATE",
                        `Updating queuesong "${songId}" failed for user ${session.userId}. "${err}"`
                    );
                    return cb({ status: "failure", message: err });
                }
                cache.runJob("PUB", { channel: "queue.update", value: songId });
                console.log(
                    "SUCCESS",
                    "QUEUE_UPDATE",
                    `User "${session.userId}" successfully update queuesong "${songId}".`
                );
                return cb({
                    status: "success",
                    message: "Successfully updated song.",
                });
            }
        );
    }),

    /**
     * Removes a queuesong
     *
     * @param {Object} session - the session object automatically added by socket.io
     * @param {String} songId - the id of the queuesong that gets removed
     * @param {Function} cb - gets called with the result
     */
    remove: hooks.adminRequired(async (session, songId, cb, userId) => {
        const queueSongModel = await db.runJob("GET_MODEL", {
            modelName: "queueSong",
        });
        async.waterfall(
            [
                (next) => {
                    queueSongModel.deleteOne({ _id: songId }, next);
                },
            ],
            async (err) => {
                if (err) {
                    err = await utils.runJob("GET_ERROR", { error: err });
                    console.log(
                        "ERROR",
                        "QUEUE_REMOVE",
                        `Removing queuesong "${songId}" failed for user ${session.userId}. "${err}"`
                    );
                    return cb({ status: "failure", message: err });
                }
                cache.runJob("PUB", {
                    channel: "queue.removedSong",
                    value: songId,
                });
                console.log(
                    "SUCCESS",
                    "QUEUE_REMOVE",
                    `User "${session.userId}" successfully removed queuesong "${songId}".`
                );
                return cb({
                    status: "success",
                    message: "Successfully updated song.",
                });
            }
        );
    }),

    /**
     * Creates a queuesong
     *
     * @param {Object} session - the session object automatically added by socket.io
     * @param {String} songId - the id of the song that gets added
     * @param {Function} cb - gets called with the result
     */
    add: hooks.loginRequired(async (session, songId, cb) => {
        let requestedAt = Date.now();
        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
        const queueSongModel = await db.runJob("GET_MODEL", {
            modelName: "queueSong",
        });

        async.waterfall(
            [
                (next) => {
                    queueSongModel.findOne({ songId }, next);
                },

                (song, next) => {
                    if (song) return next("This song is already in the queue.");
                    songModel.findOne({ songId }, next);
                },

                // Get YouTube data from id
                (song, next) => {
                    if (song) return next("This song has already been added.");
                    //TODO Add err object as first param of callback
                    utils
                        .runJob("GET_SONG_FROM_YOUTUBE", { songId })
                        .then((response) => {
                            const song = response.song;
                            song.duration = -1;
                            song.artists = [];
                            song.genres = [];
                            song.skipDuration = 0;
                            song.thumbnail = `${config.get(
                                "domain"
                            )}/assets/notes.png`;
                            song.explicit = false;
                            song.requestedBy = session.userId;
                            song.requestedAt = requestedAt;
                            next(null, song);
                        })
                        .catch(next);
                },
                /*(newSong, next) => {
				utils.getSongFromSpotify(newSong, (err, song) => {
					if (!song) next(null, newSong);
					else next(err, song);
				});
			},*/
                (newSong, next) => {
                    const song = new queueSongModel(newSong);
                    song.save({ validateBeforeSave: false }, (err, song) => {
                        if (err) return next(err);
                        next(null, song);
                    });
                },
                (newSong, next) => {
                    userModel.findOne({ _id: session.userId }, (err, user) => {
                        if (err) next(err, newSong);
                        else {
                            user.statistics.songsRequested =
                                user.statistics.songsRequested + 1;
                            user.save((err) => {
                                if (err) return next(err, newSong);
                                else next(null, newSong);
                            });
                        }
                    });
                },
            ],
            async (err, newSong) => {
                if (err) {
                    err = await utils.runJob("GET_ERROR", { error: err });
                    console.log(
                        "ERROR",
                        "QUEUE_ADD",
                        `Adding queuesong "${songId}" failed for user ${session.userId}. "${err}"`
                    );
                    return cb({ status: "failure", message: err });
                }
                cache.runJob("PUB", {
                    channel: "queue.newSong",
                    value: newSong._id,
                });
                console.log(
                    "SUCCESS",
                    "QUEUE_ADD",
                    `User "${session.userId}" successfully added queuesong "${songId}".`
                );
                return cb({
                    status: "success",
                    message: "Successfully added that song to the queue",
                });
            }
        );
    }),

    /**
     * Adds a set of songs to the queue
     *
     * @param {Object} session - the session object automatically added by socket.io
     * @param {String} url - the url of the the YouTube playlist
     * @param {Function} cb - gets called with the result
     */
    addSetToQueue: hooks.loginRequired((session, url, cb) => {
        async.waterfall(
            [
                (next) => {
                    utils
                        .runJob("GET_PLAYLIST_FROM_YOUTUBE", {
                            url,
                            musicOnly: false,
                        })
                        .then((songIds) => next(null, songIds))
                        .catch(next);
                },
                (songIds, next) => {
                    let processed = 0;
                    function checkDone() {
                        if (processed === songIds.length) next();
                    }
                    for (let s = 0; s < songIds.length; s++) {
                        lib.add(session, songIds[s], () => {
                            processed++;
                            checkDone();
                        });
                    }
                },
            ],
            async (err) => {
                if (err) {
                    err = await utils.runJob("GET_ERROR", { error: err });
                    console.log(
                        "ERROR",
                        "QUEUE_IMPORT",
                        `Importing a YouTube playlist to the queue failed for user "${session.userId}". "${err}"`
                    );
                    return cb({ status: "failure", message: err });
                } else {
                    console.log(
                        "SUCCESS",
                        "QUEUE_IMPORT",
                        `Successfully imported a YouTube playlist to the queue for user "${session.userId}".`
                    );
                    cb({
                        status: "success",
                        message: "Playlist has been successfully imported.",
                    });
                }
            }
        );
    }),
};

module.exports = lib;