import * as crypto from "crypto";
import { ZWaveErrorCodes } from "../error/ZWaveError";
import { assertZWaveError } from "../test/assertZWaveError";
import { SecurityManager2 } from "./Manager2";
import { SecurityClass } from "./SecurityClass";

describe("lib/security/Manager2", () => {
	// beforeAll(() => {
	// 	jest.useFakeTimers();
	// });

	// afterAll(() => {
	// 	jest.clearAllTimers();
	// 	jest.useRealTimers();
	// });

	function dummyInit(
		man: SecurityManager2,
		options: {
			keys?: boolean;
			nodeId?: number;
			secClass?: SecurityClass;
			multicastGroup?: number;
		} = {},
	): void {
		if (options.keys !== false) {
			man.setKey(SecurityClass.S0_Legacy, crypto.randomBytes(16));
			man.setKey(SecurityClass.S2_AccessControl, crypto.randomBytes(16));
			man.setKey(SecurityClass.S2_Authenticated, crypto.randomBytes(16));
			man.setKey(
				SecurityClass.S2_Unauthenticated,
				crypto.randomBytes(16),
			);
		}
		if (options.nodeId) {
			man.initializeSPAN(
				options.nodeId,
				options.secClass ?? SecurityClass.S2_Authenticated,
				crypto.randomBytes(16),
				crypto.randomBytes(16),
			);
		}
		if (options.multicastGroup) {
			man.assignSecurityClassMulticast(
				options.multicastGroup,
				options.secClass ?? SecurityClass.S2_Authenticated,
			);
			man.initializeMPAN(options.multicastGroup);
		}
	}

	describe("nextNonce", () => {
		it("should throw if the PRNG for the given receiver node has not been initialized", () => {
			const man = new SecurityManager2();
			assertZWaveError(() => man.nextNonce(2), {
				errorCode: ZWaveErrorCodes.Security2CC_NotInitialized,
				messageMatches: "initialized",
			});
		});

		it("should generate a 13-byte nonce otherwise", () => {
			const man = new SecurityManager2();
			dummyInit(man, {
				nodeId: 2,
				secClass: SecurityClass.S2_AccessControl,
			});

			const ret = man.nextNonce(2);
			expect(Buffer.isBuffer(ret)).toBeTrue();
			expect(ret.length).toBe(13);
		});

		it("two nonces should be different", () => {
			const man = new SecurityManager2();
			dummyInit(man, {
				nodeId: 2,
				secClass: SecurityClass.S2_AccessControl,
			});

			const nonce1 = man.nextNonce(2);
			const nonce2 = man.nextNonce(2);
			expect(nonce1).not.toEqual(nonce2);
		});
	});

	describe("initializeSPAN", () => {
		it("should throw if either entropy input does not have length 16", () => {
			const man = new SecurityManager2();
			const nodeId = 2;
			assertZWaveError(
				() =>
					man.initializeSPAN(
						nodeId,
						SecurityClass.S2_Authenticated,
						Buffer.alloc(15),
						Buffer.alloc(16),
					),
				{
					errorCode: ZWaveErrorCodes.Argument_Invalid,
					messageMatches: "16 bytes",
				},
			);

			assertZWaveError(
				() =>
					man.initializeSPAN(
						nodeId,
						SecurityClass.S2_Authenticated,
						Buffer.alloc(16),
						Buffer.alloc(1),
					),
				{
					errorCode: ZWaveErrorCodes.Argument_Invalid,
					messageMatches: "16 bytes",
				},
			);
		});

		it("should throw if the node has not been assigned a security class", () => {
			const man = new SecurityManager2();
			const nodeId = 2;
			assertZWaveError(
				() =>
					man.initializeSPAN(
						nodeId,
						SecurityClass.S2_Authenticated,
						Buffer.alloc(16),
						Buffer.alloc(16),
					),
				{
					errorCode: ZWaveErrorCodes.Security2CC_NotInitialized,
					messageMatches: "security class",
				},
			);
		});

		it("should throw if the keys for the node's security class have not been set up", () => {
			const man = new SecurityManager2();
			const nodeId = 2;
			assertZWaveError(
				() =>
					man.initializeSPAN(
						nodeId,
						SecurityClass.S2_Authenticated,
						Buffer.alloc(16),
						Buffer.alloc(16),
					),
				{
					errorCode: ZWaveErrorCodes.Security2CC_NotInitialized,
					messageMatches: "network key",
				},
			);
		});

		it("should not throw otherwise", () => {
			const man = new SecurityManager2();
			const nodeId = 2;
			dummyInit(man, {
				nodeId,
				secClass: SecurityClass.S2_Authenticated,
			});
			expect(() =>
				man.initializeSPAN(
					nodeId,
					SecurityClass.S2_Authenticated,
					Buffer.alloc(16),
					Buffer.alloc(16),
				),
			).not.toThrow();
		});
	});

	describe("setKeys", () => {
		it("throws if the network key does not have length 16", () => {
			const man = new SecurityManager2();
			assertZWaveError(
				() =>
					man.setKey(
						SecurityClass.S2_Authenticated,
						Buffer.alloc(15),
					),
				{
					errorCode: ZWaveErrorCodes.Argument_Invalid,
					messageMatches: "16 bytes",
				},
			);
		});

		it("throws if the security class is not valid", () => {
			const man = new SecurityManager2();
			assertZWaveError(() => man.setKey(-1 as any, Buffer.alloc(16)), {
				errorCode: ZWaveErrorCodes.Argument_Invalid,
				messageMatches: "security class",
			});
		});
	});

	describe("nextMPAN", () => {
		it("should throw if the MPAN state for the given multicast group has not been initialized", () => {
			const man = new SecurityManager2();
			assertZWaveError(() => man.nextMPAN(1), {
				errorCode: ZWaveErrorCodes.Security2CC_NotInitialized,
				messageMatches: "initialized",
			});
		});

		it("should throw if the multicast group has not been assigned to a security class", () => {
			const man = new SecurityManager2();
			man.initializeMPAN(1);
			assertZWaveError(() => man.nextMPAN(1), {
				errorCode: ZWaveErrorCodes.Security2CC_NotInitialized,
				messageMatches: "security class",
			});
		});

		it("should throw if the keys for the group's security class have not been set up", () => {
			const man = new SecurityManager2();
			man.assignSecurityClassMulticast(1, SecurityClass.S2_Authenticated);
			man.initializeMPAN(1);
			assertZWaveError(() => man.nextMPAN(1), {
				errorCode: ZWaveErrorCodes.Security2CC_NotInitialized,
				messageMatches: "network key",
			});
		});

		it("should generate a 16-byte buffer otherwise", () => {
			const man = new SecurityManager2();
			dummyInit(man, { multicastGroup: 1 });
			const ret = man.nextMPAN(1);

			expect(Buffer.isBuffer(ret)).toBeTrue();
			expect(ret.length).toBe(16);
		});

		it("two nonces should be different", () => {
			const man = new SecurityManager2();
			dummyInit(man, { multicastGroup: 2 });

			const nonce1 = man.nextMPAN(2);
			const nonce2 = man.nextMPAN(2);
			expect(nonce1).not.toEqual(nonce2);
		});
	});
});
