import axios from "axios";

import CoreClass from "../core";

class RateLimitter {
	/**
	 * Constructor
	 * @param {number} timeBetween - The time between each allowed WikiData request
	 */
	constructor(timeBetween) {
		this.dateStarted = Date.now();
		this.timeBetween = timeBetween;
	}

	/**
	 * Returns a promise that resolves whenever the ratelimit of a WikiData request is done
	 * @returns {Promise} - promise that gets resolved when the rate limit allows it
	 */
	continue() {
		return new Promise(resolve => {
			if (Date.now() - this.dateStarted >= this.timeBetween) resolve();
			else setTimeout(resolve, this.dateStarted + this.timeBetween - Date.now());
		});
	}

	/**
	 * Restart the rate limit timer
	 */
	restart() {
		this.dateStarted = Date.now();
	}
}

let WikiDataModule;
let DBModule;

class _WikiDataModule extends CoreClass {
	// eslint-disable-next-line require-jsdoc
	constructor() {
		super("wikidata", {
			concurrency: 10
		});

		WikiDataModule = this;
	}

	/**
	 * Initialises the activities module
	 * @returns {Promise} - returns promise (reject, resolve)
	 */
	async initialize() {
		DBModule = this.moduleManager.modules.db;

		this.genericApiRequestModel = this.GenericApiRequestModel = await DBModule.runJob("GET_MODEL", {
			modelName: "genericApiRequest"
		});

		this.rateLimiter = new RateLimitter(1100);
		this.requestTimeout = 5000;

		this.axios = axios.create();
	}

	/**
	 * Get WikiData data from entity url
	 * @param {object} payload - object that contains the payload
	 * @param {string} payload.entityUrl - entity url
	 * @returns {Promise} - returns promise (reject, resolve)
	 */
	async API_GET_DATA_FROM_ENTITY_URL(payload) {
		const { entityUrl } = payload;

		const sparqlQuery = `PREFIX entity_url: <${entityUrl}>
							SELECT DISTINCT ?YouTube_video_ID ?SoundCloud_track_ID WHERE {
								OPTIONAL { entity_url: wdt:P1651 ?YouTube_video_ID. }
								OPTIONAL { entity_url: wdt:P3040 ?SoundCloud_track_ID. }
							}`
			.replaceAll("\n", "")
			.replaceAll("\t", "");

		return WikiDataModule.runJob(
			"API_CALL",
			{
				url: "https://query.wikidata.org/sparql",
				params: {
					query: sparqlQuery
				}
			},
			this
		);
	}

	/**
	 * Get WikiData data from work id
	 * @param {object} payload - object that contains the payload
	 * @param {string} payload.workId - work id
	 * @returns {Promise} - returns promise (reject, resolve)
	 */
	async API_GET_DATA_FROM_MUSICBRAINZ_WORK(payload) {
		const { workId } = payload;

		const sparqlQuery =
			`SELECT DISTINCT ?item ?itemLabel ?YouTube_video_ID ?SoundCloud_ID ?Music_video_entity_URL WHERE {
				SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
				{
					SELECT DISTINCT ?item WHERE {
						?item p:P435 ?statement0.
						?statement0 ps:P435 "${workId}".
					}
					LIMIT 100
				}
				OPTIONAL { ?item wdt:P1651 ?YouTube_video_ID. }
				OPTIONAL { ?item wdt:P3040 ?SoundCloud_ID. }
				OPTIONAL { ?item wdt:P6718 ?Music_video_entity_URL. }
			}`
				.replaceAll("\n", "")
				.replaceAll("\t", "");

		return WikiDataModule.runJob(
			"API_CALL",
			{
				url: "https://query.wikidata.org/sparql",
				params: {
					query: sparqlQuery
				}
			},
			this
		);
	}

	/**
	 * Get WikiData data from release group id
	 * @param {object} payload - object that contains the payload
	 * @param {string} payload.releaseGroupId - release group id
	 * @returns {Promise} - returns promise (reject, resolve)
	 */
	async API_GET_DATA_FROM_MUSICBRAINZ_RELEASE_GROUP(payload) {
		const { releaseGroupId } = payload;

		const sparqlQuery =
			`SELECT DISTINCT ?item ?itemLabel ?YouTube_video_ID ?SoundCloud_ID ?Music_video_entity_URL WHERE {
				SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
				{
					SELECT DISTINCT ?item WHERE {
						?item p:P436 ?statement0.
						?statement0 ps:P436 "${releaseGroupId}".
					}
					LIMIT 100
				}
				OPTIONAL { ?item wdt:P1651 ?YouTube_video_ID. }
				OPTIONAL { ?item wdt:P3040 ?SoundCloud_ID. }
				OPTIONAL { ?item wdt:P6718 ?Music_video_entity_URL. }
			}`
				.replaceAll("\n", "")
				.replaceAll("\t", "");

		return WikiDataModule.runJob(
			"API_CALL",
			{
				url: "https://query.wikidata.org/sparql",
				params: {
					query: sparqlQuery
				}
			},
			this
		);
	}

	/**
	 * Get WikiData data from Spotify album id
	 * @param {object} payload - object that contains the payload
	 * @param {string} payload.spotifyAlbumId - Spotify album id
	 * @returns {Promise} - returns promise (reject, resolve)
	 */
	async API_GET_DATA_FROM_SPOTIFY_ALBUM(payload) {
		const { spotifyAlbumId } = payload;

		if (!spotifyAlbumId) throw new Error("Invalid Spotify album ID provided.");

		const sparqlQuery = `SELECT DISTINCT ?item ?itemLabel ?YouTube_playlist_ID ?SoundCloud_ID WHERE {
				SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
				{
					SELECT DISTINCT ?item WHERE {
						?item p:P2205 ?statement0.
						?statement0 ps:P2205 "${spotifyAlbumId}".
					}
					LIMIT 100
				}
				OPTIONAL { ?item wdt:P4300 ?YouTube_playlist_ID. }
				OPTIONAL { ?item wdt:P3040 ?SoundCloud_ID. }
			}`
			.replaceAll("\n", "")
			.replaceAll("\t", "");

		return WikiDataModule.runJob(
			"API_CALL",
			{
				url: "https://query.wikidata.org/sparql",
				params: {
					query: sparqlQuery
				}
			},
			this
		);
	}

	/**
	 * Get WikiData data from Spotify artist id
	 * @param {object} payload - object that contains the payload
	 * @param {string} payload.spotifyArtistId - Spotify artist id
	 * @returns {Promise} - returns promise (reject, resolve)
	 */
	async API_GET_DATA_FROM_SPOTIFY_ARTIST(payload) {
		const { spotifyArtistId } = payload;

		if (!spotifyArtistId) throw new Error("Invalid Spotify artist ID provided.");

		const sparqlQuery =
			`SELECT DISTINCT ?item ?itemLabel ?YouTube_channel_ID ?SoundCloud_ID ?MusicBrainz_artist_ID WHERE {
				SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
				{
					SELECT DISTINCT ?item WHERE {
						?item p:P1902 ?statement0.
						?statement0 ps:P1902 "${spotifyArtistId}".
					}
					LIMIT 100
				}
				OPTIONAL { ?item wdt:P2397 ?YouTube_channel_ID. }
				OPTIONAL { ?item wdt:P3040 ?SoundCloud_ID. }
				OPTIONAL { ?item wdt:P434 ?MusicBrainz_artist_ID. }
			}`
				.replaceAll("\n", "")
				.replaceAll("\t", "");

		return WikiDataModule.runJob(
			"API_CALL",
			{
				url: "https://query.wikidata.org/sparql",
				params: {
					query: sparqlQuery
				}
			},
			this
		);
	}

	/**
	 * Perform WikiData API call
	 * @param {object} payload - object that contains the payload
	 * @param {string} payload.url - request url
	 * @param {object} payload.params - request parameters
	 * @returns {Promise} - returns promise (reject, resolve)
	 */
	async API_CALL(payload) {
		const { url, params } = payload;

		let genericApiRequest = await WikiDataModule.GenericApiRequestModel.findOne({
			url,
			params
		});
		if (genericApiRequest) return genericApiRequest._doc.responseData;

		await WikiDataModule.rateLimiter.continue();
		WikiDataModule.rateLimiter.restart();

		const { data: responseData } = await WikiDataModule.axios.get(url, {
			params,
			headers: {
				Accept: "application/sparql-results+json"
			},
			timeout: WikiDataModule.requestTimeout
		});

		if (responseData.error) throw new Error(responseData.error);

		genericApiRequest = new WikiDataModule.GenericApiRequestModel({
			url,
			params,
			responseData,
			date: Date.now()
		});
		genericApiRequest.save();

		return responseData;
	}
}

export default new _WikiDataModule();