"use strict";

describe("Zotero.Sync.APIClient", function () {
	Components.utils.import("resource://zotero/config.js");
	
	var apiKey = Zotero.Utilities.randomString(24);
	var baseURL = "http://local.zotero/";
	var server, client;
	
	function setResponse(response) {
		setHTTPResponse(server, baseURL, response, {});
	}
	
	before(function () {
		Zotero.HTTP.mock = sinon.FakeXMLHttpRequest;
	});
	
	beforeEach(function () {
		Components.utils.import("resource://zotero/concurrentCaller.js");
		var caller = new ConcurrentCaller(1);
		caller.setLogger(msg => Zotero.debug(msg));
		caller.stopOnError = true;
		caller.onError = function (e) {
			Zotero.logError(e);
			if (e.fatal) {
				caller.stop();
				throw e;
			}
		};
		
		client = new Zotero.Sync.APIClient({
			baseURL,
			apiVersion: ZOTERO_CONFIG.API_VERSION,
			apiKey,
			caller
		})
		
		server = sinon.fakeServer.create();
		server.autoRespond = true;
	})
	
	after(function () {
		Zotero.HTTP.mock = null;
	})
	
	
	describe("#makeRequest()", function () {
		after(function () {
			sinon.restore();
		});
		
		it("should send Zotero-Schema-Version", async function () {
			server.respond(function (req) {
				if (req.method == "GET" && req.url == baseURL + "test-schema-version") {
					assert.propertyVal(
						req.requestHeaders,
						'Zotero-Schema-Version',
						Zotero.Schema.globalSchemaVersion.toString()
					);
					
					req.respond(200, {}, "");
				}
			});
			var spy = sinon.spy(Zotero.HTTP, "request");
			await client.makeRequest("GET", baseURL + "test-schema-version");
			assert.isTrue(spy.calledOnce);
		});
	});
	
	
	describe("#getGroups()", function () {
		it("should automatically fetch multiple pages of results", function* () {
			function groupJSON(groupID) {
				return {
					id: groupID,
					version: 1,
					data: {
						id: groupID,
						version: 1,
						name: "Group " + groupID
					}
				};
			}
			
			server.respond(function (req) {
				if (req.method == "GET" && req.url.startsWith(baseURL + "users/1/groups")) {
					// TODO: Use a real parser
					let matches = req.url.match(/start=(\d+)/);
					let start = matches ? parseInt(matches[1]) : null;
					matches = req.url.match(/limit=(\d+)/);
					let limit = matches ? parseInt(matches[1]) : null;
					if (start === null && limit === null) {
						req.respond(
							200,
							{
								Link: `<${baseURL}users/1/groups?limit=2&start=2>; rel="next", <${baseURL}users/1/groups?limit=2&start=4>; rel="last", <${baseURL}users/1/groups>; rel="alternate"`,
								"Total-Results": 2
							},
							JSON.stringify([
								groupJSON(1),
								groupJSON(2)
							])
						);
					}
					else if (start == 2 && limit == 2) {
						req.respond(
							200,
							{
								Link: `<${baseURL}users/1/groups?limit=2&start=4>; rel="next", <${baseURL}users/1/groups?limit=2&start=4>; rel="last", <${baseURL}users/1/groups>; rel="alternate"`,
								"Total-Results": 5
							},
							JSON.stringify([
								groupJSON(3),
								groupJSON(4)
							])
						);
					}
					else if (start == 4 && limit == 2) {
						req.respond(
							200,
							{
								Link: `<${baseURL}users/1/groups?limit=2&start=4>; rel="last", <${baseURL}users/1/groups>; rel="alternate"`,
								"Total-Results": 5
							},
							JSON.stringify([
								groupJSON(5),
							])
						);
					}
				}
			});
			
			var results = yield client.getGroups(1);
			assert.lengthOf(results, 5);
			assert.sameMembers(results.map(o => o.id), [1, 2, 3, 4, 5]);
		});
	});
	
	
	describe("Retries", function () {
		var spy;
		var delayStub;
		var delayDelay = 100;
		
		before(function () {
			delayStub = sinon.stub(Zotero.Promise, "delay").callsFake(() => {
				return new Zotero.Promise((resolve) => {
					setTimeout(resolve, delayDelay);
				});
			});
		});
		
		beforeEach(function () {
			client.rateDelayIntervals = [15, 25];
		});
		
		afterEach(function () {
			if (spy) {
				spy.restore();
			}
			delayStub.resetHistory();
		});
		
		after(function () {
			sinon.restore();
		});
		
		
		it("should retry on 429 error", async function () {
			var called = 0;
			server.respond(function (req) {
				if (req.method == "GET" && req.url == baseURL + "error") {
					if (called < 2) {
						req.respond(
							429,
							{},
							""
						);
					}
					else {
						req.respond(
							200,
							{},
							""
						);
					}
				}
				called++;
			});
			spy = sinon.spy(Zotero.HTTP, "request");
			var d = new Date();
			await client.makeRequest("GET", baseURL + "error");
			// Make sure we've paused for the expected delay twice
			assert.isAbove(new Date() - d, delayDelay * 2);
			assert.isTrue(spy.calledThrice);
			assert.equal(called, 3);
			// Slightly off because concurrentCaller sets the delay to the time remaining until the
			// previously set `pauseUntil` time, and a few milliseconds might have gone by
			assert.approximately(delayStub.args[0][0], 15 * 1000, 10);
			assert.approximately(delayStub.args[1][0], 25 * 1000, 10);
		});
	});
})