// @ts-check
const {
	beforeEach,
	beforeAll,
	expect,
	it,
	describe,
} = require("@jest/globals");
const fs = require("node:fs");
const path = require("node:path");
const forge = require("node-forge");
const { default: PKPass } = require("../lib/PKPass");

/**
 * @returns {[cert: Buffer, key: Buffer]}
 */

function generateCertificateAndPrivateKey() {
	const keys = forge.pki.rsa.generateKeyPair(2048);
	const cert = forge.pki.createCertificate();

	cert.publicKey = keys.publicKey;

	cert.serialNumber = "01";
	cert.validity.notBefore = new Date();
	cert.validity.notAfter = new Date();
	cert.validity.notAfter.setFullYear(
		cert.validity.notBefore.getFullYear() + 1,
	);

	const attrs = [
		{
			name: "commonName",
			value: "example.org",
		},
		{
			name: "countryName",
			value: "TS",
		},
		{
			shortName: "ST",
			value: "Test",
		},
		{
			name: "localityName",
			value: "Test",
		},
		{
			name: "organizationName",
			value: "Test",
		},
		{
			shortName: "OU",
			value: "Test",
		},
	];

	cert.setIssuer(attrs);
	cert.setSubject(attrs);
	cert.sign(keys.privateKey);

	return [
		Buffer.from(forge.pki.certificateToPem(cert)),
		Buffer.from(forge.pki.privateKeyToPem(keys.privateKey)),
	];
}

const [signerCertBuffer, privateKeyBuffer] = generateCertificateAndPrivateKey();

/**
 * SIGNER_CERT, SIGNER_KEY, WWDR and SIGNER_KEY_PASSPHRASE are also set
 * as secrets in Github for run tests on Github Actions
 */

const SIGNER_CERT = process.env.SIGNER_CERT || signerCertBuffer;
const SIGNER_KEY = process.env.SIGNER_KEY || privateKeyBuffer;
const WWDR =
	process.env.WWDR ||
	fs.readFileSync(path.resolve(__dirname, "../certificates/WWDR.pem"));
const SIGNER_KEY_PASSPHRASE = process.env.SIGNER_KEY_PASSPHRASE || "123456";

/**
 * @type {Record<string, Buffer>}
 */

const modelFiles = {};

const EXAMPLE_PATH_RELATIVE = "../examples/models/examplePass.pass";

/**
 * @param {string} folder
 * @returns
 */

function unpackFolder(folder) {
	const entryList = fs.readdirSync(path.resolve(__dirname, folder));

	const fileList = {};

	for (let entry of entryList) {
		const relativeFilePath = path.resolve(__dirname, folder, entry);

		const stats = fs.lstatSync(relativeFilePath);

		if (stats.isDirectory()) {
			const directoryFilesList = Object.entries(
				unpackFolder(relativeFilePath),
			);
			Object.assign(
				fileList,
				directoryFilesList.reduce((acc, [file, content]) => {
					return {
						...acc,
						[`${entry}/${file}`]: content,
					};
				}, {}),
			);
		} else {
			fileList[entry] = fs.readFileSync(relativeFilePath);
		}
	}

	return fileList;
}

function getGeneratedPassJson(pkpass) {
	const buffers = pkpass.getAsRaw();
	return JSON.parse(buffers["pass.json"].toString("utf-8"));
}

describe("PKPass", () => {
	beforeAll(() => {
		Object.assign(modelFiles, unpackFolder(EXAMPLE_PATH_RELATIVE));
	});

	/**
	 * @type {PKPass}
	 */
	let pkpass;

	beforeEach(() => {
		pkpass = new PKPass(modelFiles, {
			signerCert: SIGNER_CERT,
			signerKey: SIGNER_KEY,
			wwdr: WWDR,
			signerKeyPassphrase: SIGNER_KEY_PASSPHRASE,
		});
	});

	it("should throw an error if certificates provided are not complete or invalid", () => {
		expect(() => {
			// @ts-expect-error
			pkpass.certificates = {
				signerCert: "",
			};
		}).toThrow();

		expect(() => {
			pkpass.certificates = {
				// @ts-expect-error
				signerCert: 5,
				// @ts-expect-error
				signerKey: 3,
				wwdr: "",
			};
		}).toThrow();

		expect(() => {
			pkpass.certificates = {
				// @ts-expect-error
				signerCert: undefined,
				// @ts-expect-error
				signerKey: null,
				wwdr: "",
			};
		}).toThrow();
	});

	it("should own pkpass mimetype", () => {
		expect(pkpass.mimeType).toBe("application/vnd.apple.pkpass");
	});

	it("should throw error if a non recognized type is assigned", () => {
		expect(
			() =>
				// @ts-expect-error
				(pkpass.type = "asfdg"),
		).toThrowError();
	});

	it("should throw if fields getters are accessed without specifying a type first", () => {
		/** Resetting pass.json */
		const passjson = modelFiles["pass.json"];
		const changedPassJson = Buffer.from(
			JSON.stringify(
				Object.assign({}, JSON.parse(passjson.toString("utf-8")), {
					eventTicket: undefined,
					boardingPass: undefined,
					coupon: undefined,
					storeCard: undefined,
					generic: undefined,
					transitType: undefined,
				}),
			),
			"utf-8",
		);

		pkpass = new PKPass(
			Object.assign({}, modelFiles, { "pass.json": changedPassJson }),
			{
				signerCert: SIGNER_CERT,
				signerKey: SIGNER_KEY,
				wwdr: WWDR,
				signerKeyPassphrase: SIGNER_KEY_PASSPHRASE,
			},
		);

		expect(() => pkpass.headerFields).toThrowError();
		expect(() => pkpass.primaryFields).toThrowError();
		expect(() => pkpass.auxiliaryFields).toThrowError();
		expect(() => pkpass.secondaryFields).toThrowError();
		expect(() => pkpass.backFields).toThrowError();
		expect(() => pkpass.transitType).toThrowError();
	});

	it("should throw if transitType is set on a non-boardingPass", () => {
		pkpass.type = "eventTicket";
		expect(() => (pkpass.transitType = "PKTransitTypeAir")).toThrowError();
		expect(() => pkpass.transitType).toThrowError();
	});

	it("should throw if transitType is not specified on a boardingPass", () => {
		pkpass.type = "boardingPass";
		expect(() => pkpass.getAsRaw()).toThrowError();
	});

	it("should include the transitType if generating a boardingPass", () => {
		pkpass.type = "boardingPass";
		pkpass.transitType = "PKTransitTypeAir";

		expect(pkpass.transitType).toBe("PKTransitTypeAir");

		const passjsonGenerated = getGeneratedPassJson(pkpass);
		expect(passjsonGenerated.boardingPass).not.toBeUndefined();
		expect(passjsonGenerated.boardingPass.transitType).toBe(
			"PKTransitTypeAir",
		);
	});

	it("should import transitType and fields from a pass.json", () => {
		pkpass = new PKPass(
			{
				...modelFiles,
				"pass.json": Buffer.from(
					JSON.stringify({
						...modelFiles["pass.json"],
						boardingPass: {
							transitType: "PKTransitTypeAir",
							primaryFields: [
								{
									key: "blue",
									value: "not-blue",
								},
							],
							headerFields: [
								{
									key: "red",
									value: "not-red",
								},
							],
						},
					}),
				),
			},
			{
				signerCert: SIGNER_CERT,
				signerKey: SIGNER_KEY,
				signerKeyPassphrase: SIGNER_KEY_PASSPHRASE,
				wwdr: WWDR,
			},
		);

		const passjsonGenerated = getGeneratedPassJson(pkpass);

		expect(passjsonGenerated.boardingPass).not.toBeUndefined();
		expect(passjsonGenerated.boardingPass.transitType).toBe(
			"PKTransitTypeAir",
		);
		expect(passjsonGenerated.boardingPass.primaryFields).toBeInstanceOf(
			Array,
		);
		expect(passjsonGenerated.boardingPass.primaryFields.length).toBe(1);
	});

	it("should include fields modifications inside final pass.json", () => {
		/** Resetting fields */
		pkpass.type = "eventTicket";

		pkpass.primaryFields.push({
			key: "testField-pf",
			value: "test",
		});
		pkpass.headerFields.push({
			key: "testField-hf",
			value: "test",
		});
		pkpass.auxiliaryFields.push({
			key: "testField-af",
			value: "test",
		});
		pkpass.secondaryFields.push({
			key: "testField-sf",
			value: "test",
		});
		pkpass.backFields.push({
			key: "testField-bf",
			value: "test",
		});

		const passjsonGenerated = getGeneratedPassJson(pkpass);

		const {
			headerFields,
			primaryFields,
			auxiliaryFields,
			secondaryFields,
			backFields,
		} = passjsonGenerated.eventTicket;

		expect(primaryFields[0]).toEqual({
			key: "testField-pf",
			value: "test",
		});
		expect(headerFields[0]).toEqual({
			key: "testField-hf",
			value: "test",
		});
		expect(auxiliaryFields[0]).toEqual({
			key: "testField-af",
			value: "test",
		});
		expect(secondaryFields[0]).toEqual({
			key: "testField-sf",
			value: "test",
		});
		expect(backFields[0]).toEqual({
			key: "testField-bf",
			value: "test",
		});
	});

	it("should maintain fields addition order", () => {
		/** Resetting fields */
		pkpass.type = "eventTicket";

		pkpass.primaryFields.push(
			{
				key: "testField-pf0",
				value: "test",
			},
			{
				key: "testField-pf1",
				value: "test",
			},
			{
				key: "testField-pf2",
				value: "test",
			},
		);

		const passjsonGenerated = getGeneratedPassJson(pkpass);

		const { primaryFields } = passjsonGenerated.eventTicket;

		expect(primaryFields[0]).toEqual({
			key: "testField-pf0",
			value: "test",
		});

		expect(primaryFields[1]).toEqual({
			key: "testField-pf1",
			value: "test",
		});

		expect(primaryFields[2]).toEqual({
			key: "testField-pf2",
			value: "test",
		});
	});

	it("should omit fields with the same keys in final pass.json", () => {
		/** Resetting fields */
		pkpass.type = "eventTicket";

		pkpass.primaryFields.push({
			key: "testField-pf",
			value: "test",
		});

		pkpass.headerFields.push({
			key: "testField-pf",
			value: "test",
		});

		const passjsonGenerated = getGeneratedPassJson(pkpass);
		expect(passjsonGenerated.eventTicket.headerFields.length).toBe(0);
	});

	it("should include row property in auxiliary fields but omit it in others", () => {
		/** Resetting fields */
		pkpass.type = "eventTicket";

		pkpass.primaryFields.push({
			key: "testField-pf",
			value: "test",
			// @ts-expect-error
			row: 0,
		});

		pkpass.auxiliaryFields.push({
			key: "testField-pf",
			value: "test",
			row: 1,
		});

		const passjsonGenerated = getGeneratedPassJson(pkpass);

		expect(passjsonGenerated.eventTicket.auxiliaryFields).toBeInstanceOf(
			Array,
		);

		expect(passjsonGenerated.eventTicket.auxiliaryFields.length).toBe(1);
		expect(passjsonGenerated.eventTicket.auxiliaryFields[0].row).toBe(1);
		expect(passjsonGenerated.eventTicket.primaryFields).toBeInstanceOf(
			Array,
		);
		expect(passjsonGenerated.eventTicket.primaryFields.length).toBe(0);
	});

	it("should reset clear all the fields if the type changes", () => {
		pkpass.type = "boardingPass";

		pkpass.primaryFields.push({
			key: "testField-pf",
			value: "test",
		});
		pkpass.headerFields.push({
			key: "testField-hf",
			value: "test",
		});
		pkpass.auxiliaryFields.push({
			key: "testField-af",
			value: "test",
		});
		pkpass.secondaryFields.push({
			key: "testField-sf",
			value: "test",
		});
		pkpass.backFields.push({
			key: "testField-bf",
			value: "test",
		});

		pkpass.transitType = "PKTransitTypeAir";
		pkpass.type = "eventTicket";

		const passjsonGenerated = getGeneratedPassJson(pkpass);

		const {
			headerFields,
			primaryFields,
			secondaryFields,
			auxiliaryFields,
			backFields,
		} = passjsonGenerated.eventTicket;

		expect(headerFields).toBeInstanceOf(Array);
		expect(headerFields.length).toBe(0);

		expect(primaryFields).toBeInstanceOf(Array);
		expect(primaryFields.length).toBe(0);

		expect(secondaryFields).toBeInstanceOf(Array);
		expect(secondaryFields.length).toBe(0);

		expect(auxiliaryFields).toBeInstanceOf(Array);
		expect(auxiliaryFields.length).toBe(0);

		expect(backFields).toBeInstanceOf(Array);
		expect(backFields.length).toBe(0);
	});

	it("should export a buffer when getAsBuffer is used", () => {
		expect(pkpass.getAsBuffer()).toBeInstanceOf(Buffer);
	});

	describe("pkpass should get frozen once an export is done", () => {
		it("getAsRaw", () => {
			pkpass.getAsRaw();

			/** We might want to test all the methods, but methods might change... so should we? */
			expect(() => pkpass.localize("en", { a: "b" })).toThrowError();
		});

		it("getAsBuffer", () => {
			pkpass.getAsBuffer();

			/** We might want to test all the methods, but methods might change... so should we? */
			expect(() => pkpass.localize("en", { a: "b" })).toThrowError();
		});

		it("getAsStream", () => {
			pkpass.getAsStream();

			/** We might want to test all the methods, but methods might change... so should we? */
			expect(() => pkpass.localize("en", { a: "b" })).toThrowError();
		});
	});

	describe("localize and languages", () => {
		it("should delete a language, all of its translations and all of its files, when null is passed as parameter", () => {
			pkpass.addBuffer("it.lproj/icon@3x.png", Buffer.alloc(0));
			pkpass.addBuffer("en.lproj/icon@3x.png", Buffer.alloc(0));

			pkpass.localize("it", null);
			pkpass.localize("en", null);

			const buffers = pkpass.getAsRaw();

			expect(pkpass.languages.length).toBe(0);
			expect(buffers["it.lproj/icon@3x.png"]).toBeUndefined();
			expect(buffers["en.lproj/icon@3x.png"]).toBeUndefined();
		});

		it("should throw if lang is not a string", () => {
			// @ts-expect-error
			expect(() => pkpass.localize(null)).toThrowError();

			// @ts-expect-error
			expect(() => pkpass.localize(undefined)).toThrowError();

			// @ts-expect-error
			expect(() => pkpass.localize(5)).toThrowError();

			// @ts-expect-error
			expect(() => pkpass.localize(true)).toThrowError();

			// @ts-expect-error
			expect(() => pkpass.localize({})).toThrowError();
		});

		it("should create a new pass.strings from passed translations", () => {
			pkpass.localize("en", {
				mimmo: "Domenic",
			});

			const buffers = pkpass.getAsRaw();

			expect(buffers["en.lproj/pass.strings"].toString("utf-8")).toBe(
				'"mimmo" = "Domenic";',
			);
		});
	});

	describe("addBuffer", () => {
		it("should include a file buffer inside the final pass", () => {
			pkpass.addBuffer("icon@3x.png", modelFiles["icon.png"]);

			const buffers = pkpass.getAsRaw();

			expect(buffers["icon@3x.png"]).not.toBeUndefined();
			expect(buffers["icon@3x.png"]).toBe(modelFiles["icon.png"]);
		});

		it("should include localized files buffer inside final pass", () => {
			pkpass.addBuffer("it.lproj/icon@3x.png", modelFiles["icon.png"]);

			const buffers = pkpass.getAsRaw();

			expect(buffers["it.lproj/icon@3x.png"]).not.toBeUndefined();
			expect(buffers["it.lproj/icon@3x.png"]).toBe(
				modelFiles["icon.png"],
			);
		});

		it("should ignore further pass.json addition if already available", () => {
			expect(modelFiles["pass.json"]).not.toBeUndefined();

			pkpass.addBuffer(
				"pass.json",
				Buffer.from(
					JSON.stringify({
						boardingPass: {},
					}),
				),
			);

			const passjsonGenerated = getGeneratedPassJson(pkpass);
			expect(passjsonGenerated.boardingPass).toBeUndefined();
			expect(passjsonGenerated.eventTicket).toBeInstanceOf(Object);
		});

		it("should accept a pass.json if not already added", () => {
			const modelFilesCopy = Object.assign({}, modelFiles, {
				"pass.json": undefined,
			});

			pkpass = new PKPass(modelFilesCopy, {
				signerCert: SIGNER_CERT,
				signerKey: SIGNER_KEY,
				wwdr: WWDR,
				signerKeyPassphrase: SIGNER_KEY_PASSPHRASE,
			});

			pkpass.addBuffer(
				"pass.json",
				Buffer.from(
					JSON.stringify({
						boardingPass: {
							primaryFields: [
								{
									key: "test",
									value: "meh",
								},
							],
							transitType: "PKTransitTypeAir",
						},
						description: "my testing pass",
					}),
				),
			);

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.boardingPass).not.toBeUndefined();
			expect(passjsonGenerated.boardingPass.primaryFields[0]).toEqual({
				key: "test",
				value: "meh",
			});
			expect(passjsonGenerated.boardingPass.transitType).toBe(
				"PKTransitTypeAir",
			);
		});

		it("should accept personalization files if nfc data is added", () => {
			pkpass.setNFC({
				encryptionPublicKey: "fakeEPK",
				message: "Not-a-valid-message-but-we-dont-care",
			});

			pkpass.addBuffer(
				"personalization.json",
				Buffer.from(
					JSON.stringify({
						requiredPersonalizationFields: [
							"PKPassPersonalizationFieldName",
						],
						description: "reward enrollement test",
					}),
				),
			);

			pkpass.addBuffer(
				"personalizationLogo@2x.png",
				modelFiles["icon.png"],
			);

			const buffers = pkpass.getAsRaw();

			expect(buffers["personalization.json"]).not.toBeUndefined();
			expect(
				JSON.parse(buffers["personalization.json"].toString("utf-8"))
					.requiredPersonalizationFields,
			).not.toBeUndefined();
			expect(
				JSON.parse(buffers["personalization.json"].toString("utf-8"))
					.requiredPersonalizationFields.length,
			).toBe(1);
			expect(
				JSON.parse(buffers["personalization.json"].toString("utf-8"))
					.requiredPersonalizationFields[0],
			).toBe("PKPassPersonalizationFieldName");
		});

		it("should remove personalization files if nfc data is not specified", () => {
			pkpass.addBuffer(
				"personalization.json",
				Buffer.from(
					JSON.stringify({
						requiredPersonalizationFields: [
							"PKPassPersonalizationFieldName",
						],
						description: "reward enrollement test",
					}),
				),
			);

			pkpass.addBuffer(
				"personalizationLogo@2x.png",
				modelFiles["icon.png"],
			);

			const buffers = pkpass.getAsRaw();

			expect(buffers["personalization.json"]).toBeUndefined();
			expect(buffers["personalizationLogo@2x.png"]).toBeUndefined();
		});

		it("should convert Windows paths to unix paths", () => {
			/**
			 * This should not be reassignable, but we are actually able to set it.
			 * And this is fine for testing Windows-like behavior.
			 */

			// @ts-ignore
			path.sep = "\\";

			pkpass.addBuffer("it.lproj\\icon@2x.png", modelFiles["icon.png"]);

			const buffers = pkpass.getAsRaw();

			expect(
				JSON.parse(buffers["manifest.json"].toString("utf-8"))[
					"it.lproj/icon@2x.png"
				],
			).not.toBeUndefined();

			/** Resetting for the next tests */
			// @ts-ignore
			path.sep = "/";
		});

		it("should merge translations files with translations", () => {
			const translationFile = `"MY_DESCRIPTION" = "test";
"MY_DESCRIPTION_2" = "test";`;

			pkpass.addBuffer(
				"en.lproj/pass.strings",
				Buffer.from(translationFile),
			);

			expect(pkpass.languages.length).toBe(1);

			const buffers = pkpass.getAsRaw();

			expect(buffers["en.lproj/pass.strings"]).not.toBeUndefined();
			expect(buffers["en.lproj/pass.strings"].toString("utf-8")).toBe(
				translationFile,
			);
		});

		it("should ignore invalid l10n files", () => {
			const invalidTranslationStrings = `
"Insert Element"="Insert Element
"ErrorString_1= "An unknown error occurred."
			`;

			pkpass.addBuffer(
				"en.lproj/pass.strings",
				Buffer.from(invalidTranslationStrings),
			);

			expect(pkpass.files["en.lproj/pass.strings"]).toBeUndefined();

			const buffers = pkpass.getAsRaw();

			expect(buffers["en.lproj/pass.strings"]).toBeUndefined();
		});
	});

	describe("expiration date", () => {
		it("should set a pass expiration date", () => {
			pkpass.setExpirationDate(new Date("2023-04-09T17:00-07:00"));

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.expirationDate).toBe(
				"2023-04-10T00:00:00.000Z",
			);
		});

		it("should reset an expiration date", () => {
			pkpass.setExpirationDate(new Date(2023, 3, 10));
			pkpass.setExpirationDate(null);

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.expirationDate).toBeUndefined();
		});

		it("should throw if an invalid date is received", () => {
			expect(() =>
				// @ts-expect-error
				pkpass.setExpirationDate("32/18/228317"),
			).toThrowError();
			// @ts-expect-error
			expect(() => pkpass.setExpirationDate(undefined)).toThrowError();
			// @ts-expect-error
			expect(() => pkpass.setExpirationDate(5)).toThrowError();
			// @ts-expect-error
			expect(() => pkpass.setExpirationDate({})).toThrowError();
		});
	});

	describe("beacons", () => {
		it("should set pass beacons", () => {
			pkpass.setBeacons({
				proximityUUID: "0000000000",
				relevantText: "immabeacon",
			});

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.beacons.length).toBe(1);
			expect(passjsonGenerated.beacons).toEqual([
				{
					proximityUUID: "0000000000",
					relevantText: "immabeacon",
				},
			]);
		});

		it("should reset beacons", () => {
			pkpass.setBeacons({
				proximityUUID: "0000000000",
				relevantText: "immabeacon",
			});
			pkpass.setBeacons(null);

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.beacons).toBeUndefined();
		});
	});

	describe("locations", () => {
		it("should set pass locations", () => {
			pkpass.setLocations({
				latitude: 0,
				longitude: 0,
			});

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.locations.length).toBe(1);
			expect(passjsonGenerated.locations).toEqual([
				{
					latitude: 0,
					longitude: 0,
				},
			]);
		});

		it("should reset locations", () => {
			pkpass.setLocations({
				latitude: 0,
				longitude: 0,
			});
			pkpass.setLocations(null);

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.locations).toBeUndefined();
		});
	});

	describe("Date relevancy", () => {
		describe("(deprecated iOS 18) (root).relevantDate", () => {
			it("should set pass relevant date", () => {
				pkpass.setRelevantDate(new Date("2023-04-11T00:15+10:00"));

				const passjsonGenerated = getGeneratedPassJson(pkpass);

				expect(passjsonGenerated.relevantDate).toBe(
					"2023-04-10T14:15:00.000Z",
				);
			});

			it("should reset relevant date", () => {
				pkpass.setRelevantDate(new Date(2023, 3, 10, 14, 15));
				pkpass.setRelevantDate(null);

				const passjsonGenerated = getGeneratedPassJson(pkpass);

				expect(passjsonGenerated.relevantDate).toBeUndefined();
			});

			it("should throw if an invalid date is received", () => {
				expect(() =>
					// @ts-expect-error
					pkpass.setRelevantDate("32/18/228317"),
				).toThrowError();
				// @ts-expect-error
				expect(() => pkpass.setRelevantDate(undefined)).toThrowError();
				// @ts-expect-error
				expect(() => pkpass.setRelevantDate(5)).toThrowError();
				// @ts-expect-error
				expect(() => pkpass.setRelevantDate({})).toThrowError();
			});
		});

		describe("setRelevantDates", () => {
			it("should accept strings", () => {
				pkpass.setRelevantDates([
					{
						startDate: "2025-01-08T22:17:30.000Z",
						endDate: "2025-01-08T23:58:25.000Z",
					},
					{
						relevantDate: "2025-01-08T22:17:30.000Z",
					},
				]);

				const passjsonGenerated = getGeneratedPassJson(pkpass);

				expect(passjsonGenerated.relevantDates).toMatchObject([
					{
						startDate: "2025-01-08T22:17:30.000Z",
						endDate: "2025-01-08T23:58:25.000Z",
					},
					{
						relevantDate: "2025-01-08T22:17:30.000Z",
					},
				]);
			});

			it("should accept dates", () => {
				pkpass.setRelevantDates([
					{
						startDate: new Date(2025, 1, 8, 23, 58, 25),
						endDate: new Date(2025, 1, 8, 23, 58, 25),
					},
					{
						relevantDate: new Date(2025, 1, 8, 23, 58, 25),
					},
				]);

				const passjsonGenerated = getGeneratedPassJson(pkpass);

				expect(passjsonGenerated.relevantDates).toMatchObject([
					{
						startDate: "2025-02-08T22:58:25.000Z",
						endDate: "2025-02-08T22:58:25.000Z",
					},
					{
						relevantDate: "2025-02-08T22:58:25.000Z",
					},
				]);
			});

			it("should allow resetting", () => {
				pkpass.setRelevantDates([
					{
						startDate: "2025-01-08T22:17:30.000Z",
						endDate: "2025-01-08T23:58:25.000Z",
					},
					{
						relevantDate: "2025-01-08T22:17:30.000Z",
					},
				]);

				pkpass.setRelevantDates(null);

				const passjsonGenerated = getGeneratedPassJson(pkpass);

				expect(passjsonGenerated.relevantDates).toBeUndefined();
			});
		});
	});

	describe("barcodes", () => {
		it("should create all barcode structures if a message is used", () => {
			pkpass.setBarcodes("a test barcode");

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.barcode).toBeUndefined();
			expect(passjsonGenerated.barcodes).toBeInstanceOf(Array);
			expect(passjsonGenerated.barcodes.length).toBe(4);
			expect(passjsonGenerated.barcodes).toEqual([
				{
					format: "PKBarcodeFormatQR",
					message: "a test barcode",
					messageEncoding: "iso-8859-1",
				},
				{
					format: "PKBarcodeFormatPDF417",
					message: "a test barcode",
					messageEncoding: "iso-8859-1",
				},
				{
					format: "PKBarcodeFormatAztec",
					message: "a test barcode",
					messageEncoding: "iso-8859-1",
				},
				{
					format: "PKBarcodeFormatCode128",
					message: "a test barcode",
					messageEncoding: "iso-8859-1",
				},
			]);
		});

		it("should use only the barcode structure provided", () => {
			pkpass.setBarcodes({
				format: "PKBarcodeFormatQR",
				message: "a test barcode",
			});

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.barcode).toBeUndefined();
			expect(passjsonGenerated.barcodes).toBeInstanceOf(Array);
			expect(passjsonGenerated.barcodes.length).toBe(1);
			expect(passjsonGenerated.barcodes).toEqual([
				{
					format: "PKBarcodeFormatQR",
					message: "a test barcode",
					messageEncoding: "iso-8859-1",
				},
			]);
		});

		it("should ignore objects and values that not comply with Schema.Barcodes", () => {
			/**
			 * @type {Parameters<typeof pkpass["setBarcodes"]>}
			 */

			const setBarcodesArguments = [
				// @ts-expect-error
				5,
				// @ts-expect-error
				10,
				// @ts-expect-error
				15,
				{
					message: "28363516282",
					format: "PKBarcodeFormatPDF417",
				},
				// @ts-expect-error
				{
					format: "PKBarcodeFormatPDF417",
				},
				// @ts-expect-error
				7,
				// @ts-expect-error
				1,
			];

			pkpass.setBarcodes(...setBarcodesArguments);

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.barcodes).toBeInstanceOf(Array);
			expect(passjsonGenerated.barcodes.length).toBe(1);
			expect(passjsonGenerated.barcodes[0]).toEqual({
				message: "28363516282",
				format: "PKBarcodeFormatPDF417",
				messageEncoding: "iso-8859-1",
			});
		});
	});

	describe("nfc", () => {
		it("should set pass nfc", () => {
			pkpass.setNFC({
				encryptionPublicKey: "blabla",
				message: "nfc data",
			});

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.nfc).toEqual({
				encryptionPublicKey: "blabla",
				message: "nfc data",
			});
		});

		it("should reset nfc data", () => {
			pkpass.setNFC({
				encryptionPublicKey: "blabla",
				message: "nfc data",
			});
			pkpass.setNFC(null);

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.nfc).toBeUndefined();
		});
	});

	describe("props getter", () => {
		it("should return a copy of all props", () => {
			pkpass.setBarcodes({
				format: "PKBarcodeFormatQR",
				message: "a test barcode",
			});

			const firstPropsCheck = pkpass.props;

			pkpass.setBarcodes(null);

			expect(firstPropsCheck.barcodes).toEqual([
				{
					format: "PKBarcodeFormatQR",
					message: "a test barcode",
					messageEncoding: "iso-8859-1",
				},
			]);
		});
	});

	describe("PKPass.from", () => {
		it("should clone the properties and the buffers of another pkpass", async () => {
			const passcopy = await PKPass.from(pkpass);
			expect(pkpass).not.toBe(passcopy);

			const buffers1 = pkpass.getAsRaw();
			const buffers2 = passcopy.getAsRaw();

			const fileNames = new Set([
				...Object.keys(buffers1),
				...Object.keys(buffers2),
			]);

			for (let key in fileNames) {
				expect(buffers1[key]).not.toBeUndefined();
				expect(buffers2[key]).not.toBeUndefined();
				expect(buffers1[key]).not.toBe(buffers2[key]);
				expect(buffers1[key]).toEqual(buffers2[key]);
			}

			const passjsonGenerated1 = getGeneratedPassJson(pkpass);
			const passjsonGenerated2 = getGeneratedPassJson(passcopy);
			expect(passjsonGenerated1.eventTicket).toEqual(
				passjsonGenerated2.eventTicket,
			);
		});

		it("should throw error when falsy value is passed as source", () => {
			expect.assertions(5);

			// @ts-expect-error
			expect(PKPass.from(null)).rejects.not.toBeUndefined();
			// @ts-expect-error
			expect(PKPass.from(false)).rejects.not.toBeUndefined();
			// @ts-expect-error
			expect(PKPass.from(undefined)).rejects.not.toBeUndefined();
			// @ts-expect-error
			expect(PKPass.from("")).rejects.not.toBeUndefined();
			// @ts-expect-error
			expect(PKPass.from({})).rejects.not.toBeUndefined();
		});

		it("should read all the files from a fs model", async () => {
			pkpass = await PKPass.from({
				model: path.resolve(__dirname, EXAMPLE_PATH_RELATIVE),
				certificates: {
					signerCert: SIGNER_CERT,
					signerKey: SIGNER_KEY,
					signerKeyPassphrase: SIGNER_KEY_PASSPHRASE,
					wwdr: WWDR,
				},
			});

			const buffers = pkpass.getAsRaw();

			for (let fileName of Object.keys(buffers)) {
				/** Skipping generated files */
				if (
					fileName === "signature" ||
					fileName === "manifest.json" ||
					fileName === "pass.json"
				) {
					continue;
				}

				expect(modelFiles[fileName]).not.toBeUndefined();
				expect(modelFiles[fileName]).toEqual(buffers[fileName]);
			}
		});

		it("should throw an error if a model folder doesn't exist", () => {
			expect(() =>
				PKPass.from({
					model: path.resolve(
						__dirname,
						"this/model/doesnt/exists.pass",
					),
				}),
			).rejects.toBeInstanceOf(Error);
		});

		it("should enforce .pass model extension", async () => {
			expect(
				async () =>
					await PKPass.from({
						model: path.resolve(
							__dirname,
							"../examples/models/examplePass",
						),
						certificates: {
							signerCert: SIGNER_CERT,
							signerKey: SIGNER_KEY,
							signerKeyPassphrase: SIGNER_KEY_PASSPHRASE,
							wwdr: WWDR,
						},
					}),
			).not.toThrow();
		});

		it("should silently filter out manifest and signature files", async () => {
			pkpass = await PKPass.from({
				model: path.resolve(__dirname, EXAMPLE_PATH_RELATIVE),
				certificates: {
					signerCert: SIGNER_CERT,
					signerKey: SIGNER_KEY,
					signerKeyPassphrase: SIGNER_KEY_PASSPHRASE,
					wwdr: WWDR,
				},
			});

			pkpass.addBuffer("manifest.json", Buffer.alloc(0));
			pkpass.addBuffer("signature", Buffer.alloc(0));

			expect(pkpass.files["manifest.json"]).toBeUndefined();
			expect(pkpass.files["signature"]).toBeUndefined();
		});

		it("should accept additional properties to be added to new buffer and ignore unknown props", async () => {
			const newPass = await PKPass.from(pkpass, {
				description: "mimmoh",
				serialNumber: "626621523738123",
				// @ts-expect-error
				insert_here_invalid_unknown_parameter_name: false,
			});

			expect(newPass.props.description).toBe("mimmoh");
			expect(newPass.props.serialNumber).toBe("626621523738123");
			expect(
				// @ts-expect-error
				newPass.props.insert_here_invalid_unknown_parameter_name,
			).toBeUndefined();

			const passjsonGenerated = getGeneratedPassJson(newPass);

			expect(passjsonGenerated.description).toBe("mimmoh");
			expect(passjsonGenerated.serialNumber).toBe("626621523738123");
			expect(
				passjsonGenerated.insert_here_invalid_unknown_parameter_name,
			).toBeUndefined();
		});
	});

	describe("PKPass.pack", () => {
		it("should should throw error if not all the files passed are PKPasses", () => {
			expect(
				// @ts-expect-error
				() => PKPass.pack(pkpass, "pass.json", pkpass),
			).toThrowError();
		});

		it("should output a frozen bundle of frozen bundles", () => {
			const pkPassesBundle = PKPass.pack(pkpass, pkpass);

			const buffers = pkPassesBundle.getAsRaw();

			expect(buffers["packed-pass-1.pkpass"]).toBeInstanceOf(Buffer);
			expect(buffers["packed-pass-2.pkpass"]).toBeInstanceOf(Buffer);
			expect(pkpass.isFrozen).toBe(true);
			expect(pkPassesBundle.isFrozen).toBe(true);
		});

		it("should output a bundle with pkpasses mimetype", () => {
			const pkPassesBundle = PKPass.pack(pkpass, pkpass);
			expect(pkPassesBundle.mimeType).toBe(
				"application/vnd.apple.pkpasses",
			);
		});
	});

	describe("eventTicket new layout", () => {
		it("should contain preferredStyleSchemes if coming from an imported pass json", () => {
			const passjson = modelFiles["pass.json"];
			const changedPassJson = Buffer.from(
				JSON.stringify(
					Object.assign({}, JSON.parse(passjson.toString("utf-8")), {
						preferredStyleSchemes: [
							"posterEventTicket",
							"eventTicket",
						],
						eventTicket: {},
					}),
				),
				"utf-8",
			);

			pkpass = new PKPass(
				Object.assign({}, modelFiles, { "pass.json": changedPassJson }),
				{
					signerCert: SIGNER_CERT,
					signerKey: SIGNER_KEY,
					wwdr: WWDR,
					signerKeyPassphrase: SIGNER_KEY_PASSPHRASE,
				},
			);

			expect(pkpass.preferredStyleSchemes).toEqual([
				"posterEventTicket",
				"eventTicket",
			]);

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.preferredStyleSchemes).not.toBeUndefined();
			expect(passjsonGenerated.preferredStyleSchemes).toEqual([
				"posterEventTicket",
				"eventTicket",
			]);
		});

		it("should contain preferredStyleSchemes if coming from the setter (legacy order)", () => {
			pkpass.type = "eventTicket";

			pkpass.preferredStyleSchemes = ["eventTicket", "posterEventTicket"];

			expect(pkpass.preferredStyleSchemes).toEqual([
				"eventTicket",
				"posterEventTicket",
			]);

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.preferredStyleSchemes).not.toBeUndefined();
			expect(passjsonGenerated.preferredStyleSchemes).toEqual([
				"eventTicket",
				"posterEventTicket",
			]);
		});

		it("should contain preferredStyleSchemes if coming from the setter (new order)", () => {
			pkpass.type = "eventTicket";

			pkpass.preferredStyleSchemes = ["posterEventTicket", "eventTicket"];

			expect(pkpass.preferredStyleSchemes).toEqual([
				"posterEventTicket",
				"eventTicket",
			]);

			const passjsonGenerated = getGeneratedPassJson(pkpass);

			expect(passjsonGenerated.preferredStyleSchemes).not.toBeUndefined();
			expect(passjsonGenerated.preferredStyleSchemes).toEqual([
				"posterEventTicket",
				"eventTicket",
			]);
		});
	});

	it("preferredStyleSchemes setter should throw if pass is not an eventTicket", () => {
		pkpass.type = "boardingPass";

		expect(() => {
			pkpass.preferredStyleSchemes = ["posterEventTicket", "eventTicket"];
		}).toThrowError();
	});

	it("preferredStyleSchemes getter should throw if pass is not an eventTicket", () => {
		pkpass.type = "boardingPass";

		expect(() => {
			pkpass.preferredStyleSchemes;
		}).toThrowError();
	});
});
