import assert from 'node:assert';
import { beforeEach, describe, test } from 'node:test';

import { encoding } from 'lib0';
import { applyUpdate, Doc } from 'yjs';

import { Storage } from '../index.js';

// update binary by y.doc.text('content').insert('hello world')
// prettier-ignore
let init = Buffer.from([
  1,
  1,
  160,
  238,
  169,
  240,
  10,
  0,
  4,
  1,
  7,
  99,
  111,
  110,
  116,
  101,
  110,
  116,
  11,
  104,
  101,
  108,
  108,
  111,
  32,
  119,
  111,
  114,
  108,
  100,
  0])
describe('Test jwst storage binding', () => {
  /** @type { Storage } */
  let storage;
  beforeEach(async () => {
    storage = await Storage.connect('sqlite::memory:', true);
  });

  test('should be able to create workspace', async () => {
    const workspace = await storage.createWorkspace('test-workspace', init);

    assert(workspace.id === 'test-workspace');
    assert.deepEqual(init, await storage.load(workspace.doc.guid));
  });

  test('should not create workspace with same id', async () => {
    await storage.createWorkspace('test-workspace', init);
    await assert.rejects(
      storage.createWorkspace('test-workspace', init),
      /Workspace [\w-]+ already exists/
    );
  });

  test('should be able to delete workspace', async () => {
    const workspace = await storage.createWorkspace('test-workspace', init);

    await storage.deleteWorkspace(workspace.id);

    await assert.rejects(
      storage.load(workspace.doc.guid),
      /Doc [\w-]+ not exists/
    );
  });

  test('should be able to sync update', async () => {
    const workspace = await storage.createWorkspace('test-workspace', init);

    const update = await storage.load(workspace.doc.guid);
    assert(update !== null);

    const doc = new Doc();
    applyUpdate(doc, update);

    let text = doc.getText('content');
    assert.equal(text.toJSON(), 'hello world');

    const updates = [];
    doc.on('update', async (/** @type { UInt8Array } */ update) => {
      updates.push(Buffer.from(update));
    });

    text.insert(5, ' my');
    text.insert(14, '!');

    for (const update of updates) {
      await storage.sync(workspace.id, workspace.doc.guid, update);
    }

    const update2 = await storage.load(workspace.doc.guid);
    const doc2 = new Doc();
    applyUpdate(doc2, update2);

    text = doc2.getText('content');
    assert.equal(text.toJSON(), 'hello my world!');
  });

  test('should be able to sync update with guid encoded', async () => {
    const workspace = await storage.createWorkspace('test-workspace', init);

    const update = await storage.load(workspace.doc.guid);
    assert(update !== null);

    const doc = new Doc();
    applyUpdate(doc, update);

    let text = doc.getText('content');
    assert.equal(text.toJSON(), 'hello world');

    const updates = [];
    doc.on('update', async (/** @type { UInt8Array } */ update) => {
      const prefix = encoding.encode(encoder => {
        encoding.writeVarString(encoder, workspace.doc.guid);
      });

      updates.push(Buffer.concat([prefix, update]));
    });

    text.insert(5, ' my');
    text.insert(14, '!');

    for (const update of updates) {
      await storage.syncWithGuid(workspace.id, update);
    }

    const update2 = await storage.load(workspace.doc.guid);
    const doc2 = new Doc();
    applyUpdate(doc2, update2);

    text = doc2.getText('content');
    assert.equal(text.toJSON(), 'hello my world!');
  });

  test('should be able to store blob', async () => {
    let workspace = await storage.createWorkspace('test-workspace');
    await storage.sync(workspace.id, workspace.doc.guid, init);
    const blobId = await storage.uploadBlob(workspace.id, Buffer.from([1]));

    assert(blobId !== null);

    let list = await storage.listBlobs(workspace.id);
    assert.deepEqual(list, [blobId]);

    let blob = await storage.getBlob(workspace.id, blobId);
    assert.deepEqual(blob.data, Buffer.from([1]));
    assert.strictEqual(blob.size, 1);
    assert.equal(blob.contentType, 'application/octet-stream');

    await storage.uploadBlob(workspace.id, Buffer.from([1, 2, 3, 4, 5]));

    const spaceTaken = await storage.blobsSize(workspace.id);

    assert.equal(spaceTaken, 6);
  });
});
