const fs = require('fs');
const path = require('path');
const assert = require('assert');
const vm = require('vm');

const root = __dirname;
const read = (name) => fs.readFileSync(path.join(root, name), 'utf8');
const manifest = JSON.parse(read('manifest.json'));
const source = read('js/instagram_unload_compat_v277.js');
const background = read('background.js');

assert.strictEqual(manifest.version, '2.3.8.277');
assert.strictEqual(manifest.version_name, '2.3.8.277');

const mainScript = (manifest.content_scripts || []).find((entry) =>
  entry.world === 'MAIN' && Array.isArray(entry.js) && entry.js.includes('js/instagram_inject.js')
);
assert.ok(mainScript, 'MAIN-world Instagram content script entry missing');
assert.strictEqual(mainScript.run_at, 'document_start');
assert.strictEqual(mainScript.all_frames, true);
assert.deepStrictEqual(mainScript.js.slice(0, 2), [
  'js/instagram_unload_compat_v277.js',
  'js/instagram_inject.js'
]);
assert.ok(background.includes('files:["js/instagram_unload_compat_v277.js", "js/instagram_inject.js"], world:"MAIN"'), 'dynamic MAIN reinjection order missing');

assert.ok(source.includes('this === window && type === "unload"'));
assert.ok(source.includes('Reflect.apply(originalAdd, this, ["pagehide", listener, options])'));
assert.ok(source.includes('Reflect.apply(originalRemove, this, ["pagehide", listener, options])'));
assert.ok(source.includes('__IGBLUE_UNLOAD_COMPAT_V277__'));
assert.ok(source.includes('IGBLUE/unload_compat_v277'));
assert.ok(!source.includes('EventTarget.prototype.addEventListener'));
assert.ok(!source.includes('EventTarget.prototype.removeEventListener'));
assert.ok(!source.includes('type === "beforeunload"'));

const calls = [];
const storage = new Map();
function originalAdd(type, listener, options) {
  calls.push({ action: 'add', target: this, type, listener, options });
}
function originalRemove(type, listener, options) {
  calls.push({ action: 'remove', target: this, type, listener, options });
}
const windowObject = {
  addEventListener: originalAdd,
  removeEventListener: originalRemove
};
const logs = [];
const context = vm.createContext({
  window: windowObject,
  localStorage: {
    setItem(key, value) { storage.set(key, value); },
    getItem(key) { return storage.has(key) ? storage.get(key) : null; }
  },
  console: {
    info(...args) { logs.push(['info', ...args]); },
    warn(...args) { logs.push(['warn', ...args]); }
  },
  Date,
  JSON,
  Object,
  Reflect,
  Number,
  String
});
windowObject.window = windowObject;

vm.runInContext(source, context, { filename: 'instagram_unload_compat_v277.js' });
const firstAddWrapper = windowObject.addEventListener;
const firstRemoveWrapper = windowObject.removeEventListener;
assert.notStrictEqual(firstAddWrapper, originalAdd);
assert.notStrictEqual(firstRemoveWrapper, originalRemove);

const unloadListener = () => {};
const unloadOptions = { capture: true, once: true, passive: true };
windowObject.addEventListener('unload', unloadListener, unloadOptions);
assert.strictEqual(calls[0].action, 'add');
assert.strictEqual(calls[0].target, windowObject);
assert.strictEqual(calls[0].type, 'pagehide');
assert.strictEqual(calls[0].listener, unloadListener);
assert.strictEqual(calls[0].options, unloadOptions);

const clickListener = () => {};
windowObject.addEventListener('click', clickListener, false);
assert.strictEqual(calls[1].type, 'click');
assert.strictEqual(calls[1].listener, clickListener);
assert.strictEqual(calls[1].options, false);

windowObject.removeEventListener('unload', unloadListener, unloadOptions);
assert.strictEqual(calls[2].action, 'remove');
assert.strictEqual(calls[2].type, 'pagehide');
assert.strictEqual(calls[2].listener, unloadListener);
assert.strictEqual(calls[2].options, unloadOptions);

const foreignTarget = {};
windowObject.addEventListener.call(foreignTarget, 'unload', unloadListener, unloadOptions);
assert.strictEqual(calls[3].target, foreignTarget);
assert.strictEqual(calls[3].type, 'unload', 'non-window targets must not be remapped');

const state = windowObject.__IGBLUE_UNLOAD_COMPAT_V277__;
assert.ok(state && state.installed === true);
let snapshot = state.snapshot();
assert.strictEqual(snapshot.add_converted, 1);
assert.strictEqual(snapshot.remove_converted, 1);
assert.strictEqual(snapshot.last_error, '');
assert.ok(storage.has('IGBLUE/unload_compat_v277'));
assert.strictEqual(logs.filter((entry) => entry[0] === 'info').length, 1, 'install log must be emitted once');

vm.runInContext(source, context, { filename: 'instagram_unload_compat_v277_reinject.js' });
assert.strictEqual(windowObject.addEventListener, firstAddWrapper, 'reinjection must not wrap addEventListener again');
assert.strictEqual(windowObject.removeEventListener, firstRemoveWrapper, 'reinjection must not wrap removeEventListener again');
snapshot = state.snapshot();
assert.strictEqual(snapshot.reinjection_count, 1);
assert.strictEqual(logs.filter((entry) => entry[0] === 'info').length, 1, 'reinjection must not duplicate install log');

const failCalls = [];
function failAdd(type) { failCalls.push(['add', this, type]); }
function failRemove(type) { failCalls.push(['remove', this, type]); }
const failingWindow = {};
Object.defineProperty(failingWindow, 'addEventListener', {
  configurable: true,
  enumerable: true,
  writable: true,
  value: failAdd
});
Object.defineProperty(failingWindow, 'removeEventListener', {
  configurable: false,
  enumerable: true,
  writable: false,
  value: failRemove
});
const failContext = vm.createContext({
  window: failingWindow,
  localStorage: { setItem() {} },
  console: { info() {}, warn() {} },
  Date,
  JSON,
  Object,
  Reflect,
  Number,
  String
});
vm.runInContext(source, failContext, { filename: 'instagram_unload_compat_v277_install_failure.js' });
assert.strictEqual(failingWindow.addEventListener, failAdd, 'partial install must restore addEventListener');
assert.strictEqual(failingWindow.removeEventListener, failRemove, 'failed removeEventListener definition must preserve original');
assert.strictEqual(failingWindow.__IGBLUE_UNLOAD_COMPAT_V277__, undefined, 'failed install must not expose an installed state');

console.log('OK test_ext_unload_compat_v277');
