'use strict';

const supertest = require('supertest');
const config = require('./config')();

class RESTClient {
	/**
	 * Constructs a new agent for making HTTP requests to the MediaWiki
	 * REST API. The agent acts like a browser session and has its own
	 * cookie jar.
	 * Pass in an optional supertest agent with user session information (cookie jar)
	 * for the client to behave as a logged in user.
	 *
	 * @param {string} endpoint REST endpoint path
	 * @param {Object} agent supertest agent
	 */
	constructor(endpoint = 'rest.php/v1', agent = null) {
		this.pathPrefix = endpoint;
		if (agent) {
			this.req = agent.req;
			this.username = agent.username;
			this.userid = agent.userid;
		} else {
			this.req = supertest.agent(config.base_uri);
			this.username = '<anon>';
			this.userid = 0;
		}
	}

	/**
	 * Constructs an HTTP request to the REST API and returns the
	 * corresponding supertest Test object, which behaves like a
	 * superagent Request. It can be used like a Promise that resolves
	 * to a Response.
	 *
	 * Call end(), then(), or use await to send the request.
	 *
	 * @param {string} endpoint
	 * @param {string} method
	 * @param {Object|string} params
	 * @param {Object} headers
	 * @return {Promise<*>}
	 */
	request(endpoint, method, params = {}, headers = {}) {
		let req;
		endpoint = this.pathPrefix + endpoint;
		switch (method.toUpperCase()) {
			case 'GET':
				req = this.req.get(endpoint)
					.query(params)
					.set(headers);
				break;
			case 'POST':
				req = this.req.post(endpoint)
					.send(params)
					.set(headers);
				break;
			case 'PUT':
				req = this.req.put(endpoint)
					.send(params)
					.set(headers);
				break;
			case 'DELETE':
				req = this.req.del(endpoint)
					.query(params)
					.set(headers);
				break;
			default:
				throw new Error(`The following method is unsupported: ${ method }`);
		}

		return req;
	}

	_objectKeysToLowerCase(headers) {
		return Object.keys(headers).reduce((updatedHeaders, key) => {
			updatedHeaders[key.toLowerCase()] = headers[key];
			return updatedHeaders;
		}, {});
	}

	/**
	 * Constructs a GET request and returns the
	 * corresponding supertest Test object
	 *
	 * @param {string} endpoint
	 * @param {Object|null}params
	 * @param {Object} headers
	 * @return {Promise<*>}
	 */
	get(endpoint, params = {}, headers = {}) {
		return this.request(endpoint, 'GET', params, headers);
	}

	/**
	 * Constructs a POST request and returns the
	 * corresponding supertest Test object
	 *
	 * @param {string} endpoint
	 * @param {Object|string} params
	 * @param {Object} headers
	 * @return {Promise<*>}
	 */
	post(endpoint, params = {}, headers = {}) {
		const updatedHeaders = this._objectKeysToLowerCase(headers);
		return this.request(endpoint, 'POST', params, Object.assign({ 'content-type': 'application/json' }, updatedHeaders));
	}

	/**
	 * Constructs a PUT request and returns the
	 * corresponding supertest Test object
	 *
	 * @param {string} endpoint
	 * @param {Object|string} params
	 * @param {Object} headers
	 * @return {Promise<*>}
	 */
	put(endpoint, params = {}, headers = {}) {
		const updatedHeaders = this._objectKeysToLowerCase(headers);
		return this.request(endpoint, 'PUT', params, Object.assign({ 'content-type': 'application/json' }, updatedHeaders));
	}

	/**
	 * Constructs a DELETE request and returns the
	 * corresponding supertest Test object
	 *
	 * @param {string} endpoint
	 * @param {Object} params
	 * @param {Object} headers
	 * @return {Promise<*>}
	 */
	del(endpoint, params = {}, headers = {}) {
		return this.request(endpoint, 'DELETE', params, headers);
	}
}

module.exports = RESTClient;