import { InternalService, Microservice } from "../../src";
import { InternalServiceConflictError, InternalServiceNotFoundError } from "../../src/errors";
import { ExitCode, MicroserviceState } from "../../src/schema";

class MockInternalService1 extends InternalService<Microservice> { NAME = "1"; }
class MockInternalService2 extends InternalService<Microservice> { NAME = "2"; }

class MockedMicroservice extends Microservice {

	public readonly quitState: MicroserviceState;
	public hasBooted   = false;
	public hasStarted  = false;
	public hasRun      = false;
	public hasShutdown = false;

	public constructor(quitState: MicroserviceState) {
		super();
		this.quitState = quitState;
	}

	protected override async boot() {
		this.hasBooted = true;
		let result = await super.boot();
		this.quitState === MicroserviceState.Booting && this.quit(25);
		return result;
	}

	protected override async start() {
		this.hasStarted = true;
		let result = await super.start();
		this.quitState === MicroserviceState.Starting && this.quit(50);
		return result;
	}

	protected override async run() {
		this.hasRun = true;
		return await super.run();
	}

	protected override async shutdown() {
		this.hasShutdown = true;
		let result = super.shutdown();
		this.quitState === MicroserviceState.Quitting && this.quit(100);
		return result;
	}
}

describe("Microservice:", () => {
	describe("Internal Service Installation", () => {
		test("Install an internal service", () => {
			let microservice = new Microservice();
			microservice.installServices([MockInternalService1, MockInternalService2]);
			expect(microservice.service(MockInternalService1)).toBeInstanceOf(MockInternalService1);
			expect(microservice.service(MockInternalService2)).toBeInstanceOf(MockInternalService2);
			expect(microservice.service(MockInternalService1)).not.toBeInstanceOf(MockInternalService2);
		});
		test("Prevent installing already-installed services", () => {
			let microservice = new Microservice();
			expect(() => microservice.installServices([MockInternalService1, MockInternalService1])).toThrowError(InternalServiceConflictError);
		});
		test("Fail when accessing non-installed services", () => {
			let microservice = new Microservice();
			expect(() => microservice.service(MockInternalService1)).toThrowError(InternalServiceNotFoundError);
		});
	});
	describe("Microservice State", () => {
		// Quitting
		test("Quit before executing process", async () => {
			let microservice = new Microservice();
			microservice.quit();
			expect(microservice.state).toEqual(MicroserviceState.Finished);
			expect(await microservice.exec()).toEqual(ExitCode.BootError);
		});
		test("Quit during boot state", async () => {
			let microservice = new MockedMicroservice(MicroserviceState.Booting);
			let exitCode = await microservice.exec();
			expect(microservice.state).toEqual(MicroserviceState.Finished);
			expect(microservice.hasBooted).toBe(true);
			expect(microservice.hasShutdown).toBe(true);
			expect(microservice.hasStarted).toBe(false);
			expect(exitCode).toEqual(25);
		});
		test("Quit during starting state", async () => {
			let microservice = new MockedMicroservice(MicroserviceState.Starting);
			let exitCode = await microservice.exec();
			expect(microservice.state).toEqual(MicroserviceState.Finished);
			expect(microservice.hasBooted).toBe(true);
			expect(microservice.hasShutdown).toBe(true);
			expect(microservice.hasStarted).toBe(true);
			expect(exitCode).toEqual(50);
		});
		test("Quit during running state", async () => {
			let microservice = new MockedMicroservice(MicroserviceState.Running);
			let runningPromise = microservice.exec();
			setImmediate(() => microservice.quit(75));
			let exitCode = await runningPromise;
			expect(microservice.state).toEqual(MicroserviceState.Finished);
			expect(exitCode).toEqual(75);
		});
		test("Quit during quitting state", async () => {
			let microservice = new MockedMicroservice(MicroserviceState.Quitting);
			let runningPromise = microservice.exec();
			microservice.quit();
			let exitCode = await runningPromise;
			expect(microservice.state).toEqual(MicroserviceState.Finished);
			expect(exitCode).toEqual(0);
		});
	});
});
