import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string';
import { expect } from 'aegir/utils/chai.js';
import {
  getDescribe,
  getIt
} from '../utils/mocha.js';
import all from 'it-all';
import drain from 'it-drain';
import { CID } from 'multiformats/cid';
import { base64 } from 'multiformats/bases/base64';
async function getBaseEncodedMultihashes(ipfs) {
  const refs = await all(ipfs.refs.local());
  return refs.map(r => base64.encode(CID.parse(r.ref).multihash.bytes));
}
async function shouldHaveRef(ipfs, cid) {
  return expect(getBaseEncodedMultihashes(ipfs)).to.eventually.include(base64.encode(cid.multihash.bytes));
}
async function shouldNotHaveRef(ipfs, cid) {
  return expect(getBaseEncodedMultihashes(ipfs)).to.eventually.not.include(base64.encode(cid.multihash.bytes));
}
export function testGc(factory, options) {
  const describe = getDescribe(options);
  const it = getIt(options);
  describe('.repo.gc', () => {
    let ipfs;
    before(async () => {
      ipfs = (await factory.spawn()).api;
    });
    after(() => factory.clean());
    it('should run garbage collection', async () => {
      const res = await ipfs.add(uint8ArrayFromString('apples'));
      const pinset = await all(ipfs.pin.ls());
      expect(pinset.map(obj => obj.cid.toString())).includes(res.cid.toString());
      await ipfs.pin.rm(res.cid);
      await all(ipfs.repo.gc());
      const finalPinset = await all(ipfs.pin.ls());
      expect(finalPinset.map(obj => obj.cid.toString())).not.includes(res.cid.toString());
    });
    it('should clean up unpinned data', async () => {
      const addRes = await ipfs.add(uint8ArrayFromString('apples'));
      const cid = addRes.cid;
      await shouldHaveRef(ipfs, cid);
      await drain(ipfs.repo.gc());
      await shouldHaveRef(ipfs, cid);
      await ipfs.pin.rm(cid);
      await all(ipfs.repo.gc());
      await shouldNotHaveRef(ipfs, cid);
    });
    it('should clean up removed MFS files', async () => {
      await ipfs.files.write('/test', uint8ArrayFromString('oranges'), { create: true });
      const stats = await ipfs.files.stat('/test');
      expect(stats.type).to.equal('file');
      await shouldHaveRef(ipfs, stats.cid);
      await drain(ipfs.repo.gc());
      await shouldHaveRef(ipfs, stats.cid);
      await ipfs.files.rm('/test');
      await drain(ipfs.repo.gc());
      await shouldNotHaveRef(ipfs, stats.cid);
    });
    it('should clean up block only after unpinned and removed from MFS', async () => {
      await ipfs.files.write('/test', uint8ArrayFromString('peaches'), { create: true });
      const stats = await ipfs.files.stat('/test');
      expect(stats.type).to.equal('file');
      const mfsFileCid = stats.cid;
      const block = await ipfs.block.get(mfsFileCid);
      const addRes = await ipfs.add(block);
      const dataCid = addRes.cid;
      await shouldHaveRef(ipfs, dataCid);
      await drain(ipfs.repo.gc());
      await shouldHaveRef(ipfs, dataCid);
      await ipfs.files.rm('/test');
      await drain(ipfs.repo.gc());
      await shouldNotHaveRef(ipfs, mfsFileCid);
      await shouldHaveRef(ipfs, dataCid);
      await ipfs.pin.rm(dataCid);
      await drain(ipfs.repo.gc());
      await shouldNotHaveRef(ipfs, mfsFileCid);
      await shouldNotHaveRef(ipfs, dataCid);
    });
    it('should clean up indirectly pinned data after recursive pin removal', async () => {
      const addRes = await ipfs.add(uint8ArrayFromString('pears'));
      const dataCid = addRes.cid;
      await ipfs.pin.rm(dataCid);
      const obj = {
        Data: uint8ArrayFromString('fruit'),
        Links: [{
            Name: 'p',
            Hash: dataCid,
            Tsize: addRes.size
          }]
      };
      const objCid = await ipfs.object.put(obj);
      expect((await all(ipfs.pin.ls())).map(p => p.cid.toString())).not.includes(objCid.toString());
      await shouldHaveRef(ipfs, objCid);
      await shouldHaveRef(ipfs, dataCid);
      await ipfs.pin.add(objCid, { recursive: true });
      const pins = await all(ipfs.pin.ls());
      expect(pins.find(p => p.cid.toString() === dataCid.toString())).to.have.property('type', 'indirect');
      await drain(ipfs.repo.gc());
      await shouldHaveRef(ipfs, objCid);
      await shouldHaveRef(ipfs, dataCid);
      await ipfs.pin.rm(objCid.toString());
      await drain(ipfs.repo.gc());
      await shouldNotHaveRef(ipfs, objCid);
      await shouldNotHaveRef(ipfs, dataCid);
    });
  });
}