import config from "config";
import async from "async";
import axios from "axios";

import isLoginRequired from "../hooks/loginRequired";
import { hasPermission, useHasPermission } from "../hooks/hasPermission";

// eslint-disable-next-line
import moduleManager from "../../index";

const UtilsModule = moduleManager.modules.utils;
const WSModule = moduleManager.modules.ws;
const YouTubeModule = moduleManager.modules.youtube;
const SpotifyModule = moduleManager.modules.spotify;

export default {
	/**
	 * Fetches a list of songs from Youtube's API
	 * @param {object} session - user session
	 * @param {string} query - the query we'll pass to youtubes api
	 * @param {Function} cb - callback
	 * @returns {{status: string, data: object}} - returns an object
	 */
	searchYoutube: isLoginRequired(function searchYoutube(session, query, cb) {
		return YouTubeModule.runJob("SEARCH", { query }, this)
			.then(data => {
				this.log("SUCCESS", "APIS_SEARCH_YOUTUBE", `Searching YouTube successful with query "${query}".`);
				return cb({ status: "success", data });
			})
			.catch(async err => {
				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
				this.log("ERROR", "APIS_SEARCH_YOUTUBE", `Searching youtube failed with query "${query}". "${err}"`);
				return cb({ status: "error", message: err });
			});
	}),

	/**
	 * Fetches a specific page of search results from Youtube's API
	 * @param {object} session - user session
	 * @param {string} query - the query we'll pass to youtubes api
	 * @param {string} pageToken - identifies a specific page in the result set that should be retrieved
	 * @param {Function} cb - callback
	 * @returns {{status: string, data: object}} - returns an object
	 */
	searchYoutubeForPage: isLoginRequired(function searchYoutubeForPage(session, query, pageToken, cb) {
		return YouTubeModule.runJob("SEARCH", { query, pageToken }, this)
			.then(data => {
				this.log(
					"SUCCESS",
					"APIS_SEARCH_YOUTUBE_FOR_PAGE",
					`Searching YouTube successful with query "${query}".`
				);
				return cb({ status: "success", data });
			})
			.catch(async err => {
				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
				this.log(
					"ERROR",
					"APIS_SEARCH_YOUTUBE_FOR_PAGE",
					`Searching youtube failed with query "${query}". "${err}"`
				);
				return cb({ status: "error", message: err });
			});
	}),

	/**
	 * Gets Discogs data
	 * @param session
	 * @param query - the query
	 * @param {Function} cb
	 */
	searchDiscogs: useHasPermission("apis.searchDiscogs", function searchDiscogs(session, query, page, cb) {
		async.waterfall(
			[
				next => {
					const options = {
						params: { q: query, per_page: 20, page },
						headers: {
							"User-Agent": "Request",
							Authorization: `Discogs key=${config.get("apis.discogs.client")}, secret=${config.get(
								"apis.discogs.secret"
							)}`
						}
					};

					axios
						.get("https://api.discogs.com/database/search", options)
						.then(res => next(null, res.data))
						.catch(err => next(err));
				}
			],
			async (err, body) => {
				if (err) {
					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
					this.log(
						"ERROR",
						"APIS_SEARCH_DISCOGS",
						`Searching discogs failed with query "${query}". "${err}"`
					);
					return cb({ status: "error", message: err });
				}
				this.log(
					"SUCCESS",
					"APIS_SEARCH_DISCOGS",
					`User "${session.userId}" searched Discogs succesfully for query "${query}".`
				);
				return cb({
					status: "success",
					data: {
						results: body.results,
						pages: body.pagination.pages
					}
				});
			}
		);
	}),

	/**
	 * Gets alternative media sources for list of Spotify tracks (media sources)
	 * @param session
	 * @param trackId - the trackId
	 * @param {Function} cb
	 */
	getAlternativeMediaSourcesForTracks: useHasPermission(
		"spotify.getAlternativeMediaSourcesForTracks",
		function getAlternativeMediaSourcesForTracks(session, mediaSources, collectAlternativeMediaSourcesOrigins, cb) {
			async.waterfall(
				[
					next => {
						if (!mediaSources) {
							next("Invalid mediaSources provided.");
							return;
						}

						next();
					},

					next => {
						this.keepLongJob();
						this.publishProgress({
							status: "started",
							title: "Getting alternative media sources for Spotify tracks",
							message: "Starting up",
							id: this.toString()
						});

						SpotifyModule.runJob(
							"GET_ALTERNATIVE_MEDIA_SOURCES_FOR_TRACKS",
							{
								mediaSources,
								collectAlternativeMediaSourcesOrigins
							},
							this
						)
							.then(() => next())
							.catch(next);
					}
				],
				async err => {
					if (err) {
						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
						this.log(
							"ERROR",
							"APIS_GET_ALTERNATIVE_SOURCES",
							`Getting alternative sources failed for "${mediaSources.join(", ")}". "${err}"`
						);
						return cb({ status: "error", message: err });
					}
					this.log(
						"SUCCESS",
						"APIS_GET_ALTERNATIVE_SOURCES",
						`User "${session.userId}" started getting alternatives for "${mediaSources.join(", ")}".`
					);
					return cb({
						status: "success"
					});
				}
			);
		}
	),

	/**
	 * Gets alternative album sources (such as YouTube playlists) for a list of Spotify album ids
	 * @param session
	 * @param trackId - the trackId
	 * @param {Function} cb
	 */
	getAlternativeAlbumSourcesForAlbums: useHasPermission(
		"spotify.getAlternativeAlbumSourcesForAlbums",
		function getAlternativeAlbumSourcesForAlbums(session, albumIds, collectAlternativeAlbumSourcesOrigins, cb) {
			async.waterfall(
				[
					next => {
						if (!albumIds) {
							next("Invalid albumIds provided.");
							return;
						}

						next();
					},

					next => {
						this.keepLongJob();
						this.publishProgress({
							status: "started",
							title: "Getting alternative album sources for Spotify albums",
							message: "Starting up",
							id: this.toString()
						});

						SpotifyModule.runJob(
							"GET_ALTERNATIVE_ALBUM_SOURCES_FOR_ALBUMS",
							{ albumIds, collectAlternativeAlbumSourcesOrigins },
							this
						)
							.then(() => next())
							.catch(next);
					}
				],
				async err => {
					if (err) {
						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
						this.log(
							"ERROR",
							"APIS_GET_ALTERNATIVE_ALBUM_SOURCES",
							`Getting alternative album sources failed for "${albumIds.join(", ")}". "${err}"`
						);
						return cb({ status: "error", message: err });
					}
					this.log(
						"SUCCESS",
						"APIS_GET_ALTERNATIVE_ALBUM_SOURCES",
						`User "${session.userId}" started getting alternative album spirces for "${albumIds.join(
							", "
						)}".`
					);
					return cb({
						status: "success"
					});
				}
			);
		}
	),

	/**
	 * Gets a list of alternative artist sources (such as YouTube channels) for a list of Spotify artist ids
	 * @param session
	 * @param trackId - the trackId
	 * @param {Function} cb
	 */
	getAlternativeArtistSourcesForArtists: useHasPermission(
		"spotify.getAlternativeArtistSourcesForArtists",
		function getAlternativeArtistSourcesForArtists(session, artistIds, collectAlternativeArtistSourcesOrigins, cb) {
			async.waterfall(
				[
					next => {
						if (!artistIds) {
							next("Invalid artistIds provided.");
							return;
						}

						next();
					},

					next => {
						this.keepLongJob();
						this.publishProgress({
							status: "started",
							title: "Getting alternative artist sources for Spotify artists",
							message: "Starting up",
							id: this.toString()
						});

						SpotifyModule.runJob(
							"GET_ALTERNATIVE_ARTIST_SOURCES_FOR_ARTISTS",
							{
								artistIds,
								collectAlternativeArtistSourcesOrigins
							},
							this
						)
							.then(() => next())
							.catch(next);
					}
				],
				async err => {
					if (err) {
						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
						this.log(
							"ERROR",
							"APIS_GET_ALTERNATIVE_ARTIST_SOURCES",
							`Getting alternative artist sources failed for "${artistIds.join(", ")}". "${err}"`
						);
						return cb({ status: "error", message: err });
					}
					this.log(
						"SUCCESS",
						"APIS_GET_ALTERNATIVE_ARTIST_SOURCES",
						`User "${session.userId}" started getting alternative artist spirces for "${artistIds.join(
							", "
						)}".`
					);
					return cb({
						status: "success"
					});
				}
			);
		}
	),

	/**
	 * Joins a room
	 * @param {object} session - user session
	 * @param {string} room - the room to join
	 * @param {Function} cb - callback
	 */
	joinRoom(session, room, cb) {
		const roomName = room.split(".")[0];
		// const roomId = room.split(".")[1];
		const rooms = {
			home: null,
			news: null,
			profile: null,
			"view-media": null,
			"manage-station": null,
			// "manage-station": "stations.view",
			"edit-song": "songs.update",
			"edit-songs": "songs.update",
			"import-album": "songs.update",
			// "edit-playlist": "playlists.update",
			"view-report": "reports.get",
			"edit-user": "users.update",
			"view-api-request": "youtube.getApiRequest",
			"view-punishment": "punishments.get"
		};
		const join = (status, error) => {
			if (status === "success")
				WSModule.runJob("SOCKET_JOIN_ROOM", {
					socketId: session.socketId,
					room
				})
					.then(() => cb({ status: "success", message: "Successfully joined room." }))
					.catch(err => join("error", err.message));
			else {
				this.log("ERROR", `Joining room failed: ${error}`);
				cb({ status: "error", message: error });
			}
		};
		if (rooms[roomName] === null) join("success");
		else if (rooms[roomName])
			hasPermission(rooms[roomName], session)
				.then(() => join("success"))
				.catch(err => join("error", err));
		else join("error", "Room not found");
	},

	/**
	 * Leaves a room
	 * @param {object} session - user session
	 * @param {string} room - the room to leave
	 * @param {Function} cb - callback
	 */
	leaveRoom(session, room, cb) {
		if (
			room === "home" ||
			room.startsWith("profile.") ||
			room.startsWith("manage-station.") ||
			room.startsWith("edit-song.") ||
			room.startsWith("view-report.") ||
			room === "import-album" ||
			room === "edit-songs"
		) {
			WSModule.runJob("SOCKET_LEAVE_ROOM", {
				socketId: session.socketId,
				room
			})
				.then(() => {})
				.catch(err => {
					this.log("ERROR", `Leaving room failed: ${err.message}`);
				});
		}

		cb({ status: "success", message: "Successfully left room." });
	},

	/**
	 * Joins an admin room
	 * @param {object} session - user session
	 * @param {string} page - the admin room to join
	 * @param {Function} cb - callback
	 */
	joinAdminRoom(session, page, cb) {
		if (
			page === "songs" ||
			page === "stations" ||
			page === "reports" ||
			page === "news" ||
			page === "playlists" ||
			page === "users" ||
			page === "statistics" ||
			page === "punishments" ||
			page === "youtube" ||
			page === "youtubeVideos" ||
			page === "youtubeChannels" ||
			(config.get("experimental.soundcloud") && (page === "soundcloud" || page === "soundcloudTracks")) ||
			page === "import" ||
			page === "dataRequests"
		) {
			hasPermission(`admin.view.${page}`, session.userId)
				.then(() =>
					WSModule.runJob("SOCKET_LEAVE_ROOMS", { socketId: session.socketId }).then(() => {
						WSModule.runJob(
							"SOCKET_JOIN_ROOM",
							{
								socketId: session.socketId,
								room: `admin.${page}`
							},
							this
						).then(() => cb({ status: "success", message: "Successfully joined admin room." }));
					})
				)
				.catch(() => cb({ status: "error", message: "Failed to join admin room." }));
		}
	},

	/**
	 * Leaves all rooms
	 * @param {object} session - user session
	 * @param {Function} cb - callback
	 */
	leaveRooms(session, cb) {
		WSModule.runJob("SOCKET_LEAVE_ROOMS", { socketId: session.socketId });

		cb({ status: "success", message: "Successfully left all rooms." });
	},

	/**
	 * Returns current date
	 * @param {object} session - user session
	 * @param {Function} cb - callback
	 */
	ping(session, cb) {
		cb({ status: "success", message: "Successfully pinged.", data: { date: Date.now() } });
	}
};
