import { expect } from "chai";
import { SinonSandbox, createSandbox, SinonStub } from "sinon";
import { Client, ChannelManager } from "discord.js";
import { BaseMocks } from "@lambocreeper/mock-discord.js";
import axios from "axios";
import App from "../src/app";
import DirectoryUtils from "../src/utils/DirectoryUtils";
import MockHandler from "./MockHandler";
import { AUTHENTICATION_MESSAGE_CHANNEL, AUTHENTICATION_MESSAGE_ID, PRODUCTION_ENV } from "../src/config.json";

describe("App", () => {
	let sandbox: SinonSandbox;
	let loginStub: SinonStub;
	let getStub: SinonStub;

	beforeEach(() => {
		sandbox = createSandbox();

		loginStub = sandbox.stub(Client.prototype, "login");
		getStub = sandbox.stub(axios, "get").resolves();

		process.env.DISCORD_TOKEN = "FAKE_TOKEN";
		process.env.HEALTH_CHECK_URL = "https://health-check.com";
	});

	describe("constructor()", () => {
		it("should throw error if DISCORD_TOKEN is not set", async () => {
			process.env.DISCORD_TOKEN = undefined;

			try {
				await new App();
			} catch ({ message }) {
				expect(message).to.equal("You must supply the DISCORD_TOKEN environment variable.");
			}
		});
	});

	describe("reportHealth()", () => {
		it("sends a GET request to a health check endpoint", () => {
			new App().reportHealth();

			expect(getStub.calledOnce).to.be.true;
			expect(getStub.calledWith("https://health-check.com")).to.be.true;
		});
	});

	describe("init()", () => {
		it("should login with the provided DISCORD_TOKEN", async () => {
			sandbox.stub(DirectoryUtils, "getFilesInDirectory").resolves([]);

			await new App().init();

			expect(loginStub.calledWith("FAKE_TOKEN")).to.be.true;
		});

		it("should look for slash commands", async () => {
			const getFilesStub = sandbox.stub(DirectoryUtils, "getFilesInDirectory").resolves([]);

			await new App().init();

			expect(getFilesStub.args[0][1]).to.equal("Command.js");
		});

		it("should look for handler files", async () => {
			const getFilesStub = sandbox.stub(DirectoryUtils, "getFilesInDirectory").resolves([]);

			await new App().init();

			expect(getFilesStub.args[1][1]).to.equal("Handler.js");
		});

		it("should bind handlers to events", async () => {
			// eslint-disable-next-line global-require
			sandbox.stub(DirectoryUtils, "getFilesInDirectory").callsFake(async () => [require("./MockHandler")]);
			const onStub = sandbox.stub(Client.prototype, "on");

			await new App().init();

			const mockHandler = new MockHandler();

			expect(onStub.calledWith(mockHandler.getEvent())).to.be.true;
		});

		it("should fetch auth channel and messages in production environment", async () => {
			const testEnv = process.env.NODE_ENV;

			process.env.NODE_ENV = PRODUCTION_ENV;

			sandbox.stub(DirectoryUtils, "getFilesInDirectory").callsFake(() => []);

			const textChannel = BaseMocks.getTextChannel();

			const fetchChannelsStub = sandbox.stub(ChannelManager.prototype, "fetch").callsFake(async () => textChannel);
			const fetchMessagesStub = sandbox.stub(textChannel.messages, "fetch");

			await new App().init();

			expect(fetchChannelsStub.calledWith(AUTHENTICATION_MESSAGE_CHANNEL)).to.be.true;
			expect(fetchMessagesStub.calledWith(AUTHENTICATION_MESSAGE_ID)).to.be.true;

			process.env.NODE_ENV = testEnv;
		});
	});

	afterEach(() => {
		sandbox.restore();
	});
});