'use strict';

const { skipIfSQLiteMissing, spawnPromisified } = require('../common');
skipIfSQLiteMissing();
const tmpdir = require('../common/tmpdir');
const assert = require('node:assert');
const { join } = require('node:path');
const { readdir } = require('node:fs/promises');
const { test, describe } = require('node:test');
let cnt = 0;

tmpdir.refresh();

function nextLocalStorage() {
  return join(tmpdir.path, `${++cnt}.localstorage`);
}

test('Storage instances cannot be created in userland', async () => {
  const cp = await spawnPromisified(process.execPath, [
    '-e', 'new globalThis.Storage()',
  ]);

  assert.strictEqual(cp.code, 1);
  assert.strictEqual(cp.signal, null);
  assert.strictEqual(cp.stdout, '');
  assert.match(cp.stderr, /Error: Illegal constructor/);
});

test('calling "length" getter on invalid this throws', async () => {
  assert.throws(() => Storage.prototype.length, TypeError);
  const { get } = Object.getOwnPropertyDescriptor(Storage.prototype, 'length');
  for (const thisArg of [null, undefined, 1n, -0, NaN, true, false, '', [], {}, Symbol()]) {
    assert.throws(() => get.call(thisArg), TypeError);
  }
});

test('sessionStorage is not persisted', async () => {
  let cp = await spawnPromisified(process.execPath, [
    '-pe', 'sessionStorage.foo = "barbaz"',
  ]);
  assert.strictEqual(cp.code, 0);
  assert.match(cp.stdout, /barbaz/);

  cp = await spawnPromisified(process.execPath, [
    '-pe', 'sessionStorage.foo',
  ]);
  assert.strictEqual(cp.code, 0);
  assert.match(cp.stdout, /undefined/);
  assert.strictEqual((await readdir(tmpdir.path)).length, 0);
});

test('localStorage returns undefined and warns without --localstorage-file', async () => {
  const cp = await spawnPromisified(process.execPath, [
    '-pe', 'localStorage',
  ]);
  assert.strictEqual(cp.code, 0);
  assert.strictEqual(cp.signal, null);
  assert.match(cp.stdout, /undefined/);
  assert.match(cp.stderr, /ExperimentalWarning:.*localStorage is not available/);
});

test('localStorage is not enumerable without --localstorage-file', async () => {
  const cp = await spawnPromisified(process.execPath, [
    '-pe', 'Object.keys(globalThis).includes("localStorage")',
  ]);
  assert.strictEqual(cp.code, 0);
  assert.match(cp.stdout, /false/);
});

test('localStorage is not persisted if it is unused', async () => {
  const cp = await spawnPromisified(process.execPath, [
    '--localstorage-file', nextLocalStorage(),
    '-pe', 'localStorage === globalThis.localStorage',
  ]);
  assert.strictEqual(cp.code, 0);
  assert.match(cp.stdout, /true/);
  assert.strictEqual((await readdir(tmpdir.path)).length, 0);
});

test('localStorage is persisted if it is used', async () => {
  const localStorageFile = nextLocalStorage();
  let cp = await spawnPromisified(process.execPath, [
    '--localstorage-file', localStorageFile,
    '-pe', 'localStorage.foo = "barbaz"',
  ]);
  assert.strictEqual(cp.code, 0);
  assert.match(cp.stdout, /barbaz/);
  const entries = await readdir(tmpdir.path);
  assert.strictEqual(entries.length, 1);
  assert.match(entries[0], /\d+\.localstorage/);

  cp = await spawnPromisified(process.execPath, [
    '--localstorage-file', localStorageFile,
    '-pe', 'localStorage.foo',
  ]);
  assert.strictEqual(cp.code, 0);
  assert.match(cp.stdout, /barbaz/);
});


describe('webstorage quota for localStorage and sessionStorage', () => {
  const MAX_STORAGE_SIZE = 10 * 1024 * 1024;

  for (const storage of ['localStorage', 'sessionStorage']) {
    test(`${storage} can store a max of 10 MB quota`, async () => {
      const args = [];
      if (storage === 'localStorage') {
        args.push('--localstorage-file', nextLocalStorage());
      }
      // Each character is 2 bytes
      args.push('-e', `
      const assert = require('assert');
      ${storage}['a'.repeat(${MAX_STORAGE_SIZE} / 2)] = '';
      assert.throws(
        () => { ${storage}.anything = 'should fail'; },
        (err) => {
          assert.strictEqual(err.name, 'QuotaExceededError');
          assert.strictEqual(err.code, 22);
          assert(err instanceof DOMException);
          assert(err instanceof QuotaExceededError);
          assert.strictEqual(err.quota, null);
          assert.strictEqual(err.requested, null);
          return true;
        },
      );
      `);
      const cp = await spawnPromisified(process.execPath, args);
      assert.strictEqual(cp.code, 0);
    });
  }
});

test('disabled with --no-webstorage', async () => {
  for (const api of ['Storage', 'localStorage', 'sessionStorage']) {
    const cp = await spawnPromisified(process.execPath, [
      '--no-webstorage',
      '--localstorage-file',
      './test/fixtures/localstoragefile-global-test',
      '-e',
      api,
    ]);

    assert.strictEqual(cp.code, 1);
    assert.strictEqual(cp.signal, null);
    assert.strictEqual(cp.stdout, '');
    assert(cp.stderr.includes(`ReferenceError: ${api} is not defined`));
  }
});
