import * as codec from '../src/codecs/json.js';
import { sha256 as hasher } from '../src/hashes/sha2.js';
import * as main from '../src/block.js';
import {
  CID,
  bytes
} from '../src/index.js';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);
const {assert} = chai;
const fixture = { hello: 'world' };
const link = CID.parse('bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae');
const buff = bytes.fromString('sadf');
describe('block', () => {
  it('basic encode/decode roundtrip', async () => {
    const block = await main.encode({
      value: fixture,
      codec,
      hasher
    });
    const block2 = await main.decode({
      bytes: block.bytes,
      codec,
      hasher
    });
    assert.deepStrictEqual(block.cid.equals(block2.cid), true);
    assert.deepStrictEqual(block.cid.equals(block2.cid), true);
    assert.deepStrictEqual(fixture, block2.value);
    const block3 = await main.create({
      bytes: block.bytes,
      cid: block.cid,
      codec,
      hasher
    });
    assert.deepStrictEqual(block3.cid.equals(block2.cid), true);
  });
  it('createUnsafe', async () => {
    const block = await main.encode({
      value: fixture,
      codec,
      hasher
    });
    const block2 = main.createUnsafe({
      bytes: block.bytes,
      cid: block.cid,
      codec
    });
    assert.deepStrictEqual(block.cid.equals(block2.cid), true);
  });
  describe('reader', () => {
    const value = {
      link,
      nope: 'skip',
      arr: [link],
      obj: { arr: [{ obj: {} }] },
      bytes: Uint8Array.from('1234')
    };
    const block = main.createUnsafe({
      value,
      codec,
      hasher,
      cid: true,
      bytes: true
    });
    it('links', () => {
      const expected = [
        'link',
        'arr/0'
      ];
      for (const [path, cid] of block.links()) {
        assert.deepStrictEqual(path, expected.shift());
        assert.deepStrictEqual(cid.toString(), link.toString());
      }
    });
    it('tree', () => {
      const expected = [
        'link',
        'nope',
        'arr',
        'arr/0',
        'obj',
        'obj/arr',
        'obj/arr/0',
        'obj/arr/0/obj',
        'bytes'
      ];
      for (const path of block.tree()) {
        assert.deepStrictEqual(path, expected.shift());
      }
    });
    it('get', () => {
      let ret = block.get('link/test');
      assert.deepStrictEqual(ret.remaining, 'test');
      assert.deepStrictEqual(ret.value.toString(), link.toString());
      ret = block.get('nope');
      assert.deepStrictEqual(ret, { value: 'skip' });
    });
    it('null links/tree', () => {
      const block = main.createUnsafe({
        value: null,
        codec,
        hasher,
        bytes: true,
        cid: true
      });
      for (const x of block.tree()) {
        throw new Error(`tree should have nothing, got "${ x }"`);
      }
      for (const x of block.links()) {
        throw new Error(`links should have nothing, got "${ x }"`);
      }
    });
  });
  it('kitchen sink', () => {
    const sink = {
      one: {
        two: {
          arr: [
            true,
            false,
            null
          ],
          three: 3,
          buff,
          link
        }
      }
    };
    const block = main.createUnsafe({
      value: sink,
      codec,
      bytes: true,
      cid: true
    });
    assert.deepStrictEqual(sink, block.value);
  });
  describe('errors', () => {
    it('constructor missing args', () => {
      assert.throws(() => new main.Block({}), 'Missing required argument');
    });
    it('encode', async () => {
      await assert.isRejected(main.encode({}), 'Missing required argument "value"');
      await assert.isRejected(main.encode({ value: true }), 'Missing required argument: codec or hasher');
    });
    it('decode', async () => {
      await assert.isRejected(main.decode({}), 'Missing required argument "bytes"');
      await assert.isRejected(main.decode({ bytes: true }), 'Missing required argument: codec or hasher');
    });
    it('createUnsafe', async () => {
      assert.throws(() => main.createUnsafe({}), 'Missing required argument, must either provide "value" or "codec"');
    });
    it('create', async () => {
      await assert.isRejected(main.create({}), 'Missing required argument "bytes"');
      await assert.isRejected(main.create({ bytes: true }), 'Missing required argument "hasher"');
      const block = await main.encode({
        value: fixture,
        codec,
        hasher
      });
      const block2 = await main.encode({
        value: {
          ...fixture,
          test: 'blah'
        },
        codec,
        hasher
      });
      await assert.isRejected(main.create({
        bytes: block.bytes,
        cid: block2.cid,
        codec,
        hasher
      }), 'CID hash does not match bytes');
    });
    it('get', async () => {
      const block = await main.encode({
        value: fixture,
        codec,
        hasher
      });
      assert.throws(() => block.get('/asd/fs/dfasd/f'), 'Object has no property at ["asd"]');
    });
  });
});