import { tap, expect } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv';
import * as smartmongo from '@push.rocks/smartmongo';
import { smartunique } from '../ts/plugins.js';

import * as mongodb from 'mongodb';

const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');

console.log(process.memoryUsage());

// the tested module
import * as smartdata from '../ts/index.js';

// =======================================
// Connecting to the database server
// =======================================

let smartmongoInstance: smartmongo.SmartMongo;
let testDb: smartdata.SmartdataDb;

const totalCars = 2000;

tap.test('should create a testinstance as database', async () => {
  smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
  testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
  await testDb.init();
});

tap.skip.test('should connect to atlas', async (tools) => {
  const databaseName = `test-smartdata-${smartunique.shortId()}`;
  testDb = new smartdata.SmartdataDb({
    mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGO_URL'),
    mongoDbName: databaseName,
  });
  await testDb.init();
});

// =======================================
// The actual tests
// =======================================

// ------
// Collections
// ------

@smartdata.Collection(() => {
  return testDb;
})
class Car extends smartdata.SmartDataDbDoc<Car, Car> {
  @smartdata.unI()
  public index: string = smartunique.shortId();

  @smartdata.svDb()
  public color: string;

  @smartdata.svDb()
  public brand: string;

  @smartdata.svDb()
  public testBuffer = Buffer.from('hello');

  @smartdata.svDb()
  deepData = {
    sodeep: 'yes',
  };

  constructor(colorArg: string, brandArg: string) {
    super();
    this.color = colorArg;
    this.brand = brandArg;
  }
}

tap.test('should create a new id', async () => {
  const newid = await Car.getNewId();
  console.log(newid);
});

tap.test('should save the car to the db', async (toolsArg) => {
  const myCar = new Car('red', 'Volvo');
  await myCar.save();

  const myCar2 = new Car('red', 'Volvo');
  await myCar2.save();

  let counter = 0;

  const gottenCarInstance = await Car.getInstance({});
  console.log(gottenCarInstance.testBuffer instanceof mongodb.Binary);
  process.memoryUsage();
  do {
    const myCar3 = new Car('red', 'Renault');
    await myCar3.save();
    counter++;
    if (counter % 100 === 0) {
      console.log(
        `Filled database with ${counter} of ${totalCars} Cars and memory usage ${
          process.memoryUsage().rss / 1e6
        } MB`,
      );
    }
  } while (counter < totalCars);
  console.log(process.memoryUsage());
});

tap.test('expect to get instance of Car with shallow match', async () => {
  const totalQueryCycles = totalCars / 2;
  let counter = 0;
  do {
    const timeStart = Date.now();
    const myCars = await Car.getInstances({
      brand: 'Renault',
    });
    if (counter % 10 === 0) {
      console.log(
        `performed ${counter} of ${totalQueryCycles} total query cycles: took ${
          Date.now() - timeStart
        }ms to query a set of 2000 with memory footprint ${process.memoryUsage().rss / 1e6} MB`,
      );
    }
    expect(myCars[0].deepData.sodeep).toEqual('yes');
    expect(myCars[0].brand).toEqual('Renault');
    counter++;
  } while (counter < totalQueryCycles);
});

tap.test('expect to get instance of Car with deep match', async () => {
  const totalQueryCycles = totalCars / 6;
  let counter = 0;
  do {
    const timeStart = Date.now();
    const myCars2 = await Car.getInstances({
      deepData: {
        sodeep: 'yes',
      },
    });
    if (counter % 10 === 0) {
      console.log(
        `performed ${counter} of ${totalQueryCycles} total query cycles: took ${
          Date.now() - timeStart
        }ms to deep query a set of 2000 with memory footprint ${process.memoryUsage().rss / 1e6} MB`,
      );
    }
    expect(myCars2[0].deepData.sodeep).toEqual('yes');
    expect(myCars2[0].brand).toEqual('Volvo');
    counter++;
  } while (counter < totalQueryCycles);
});

tap.test('expect to get instance of Car and update it', async () => {
  const myCar = await Car.getInstance<Car>({
    brand: 'Volvo',
  });
  expect(myCar.color).toEqual('red');
  myCar.color = 'blue';
  await myCar.save();
});

tap.test('should be able to delete an instance of car', async () => {
  const myCars = await Car.getInstances({
    brand: 'Volvo',
    color: 'blue',
  });
  console.log(myCars);
  expect(myCars[0].color).toEqual('blue');
  for (const myCar of myCars) {
    await myCar.delete();
  }

  const myCar2 = await Car.getInstance<Car>({
    brand: 'Volvo',
  });
  expect(myCar2.color).toEqual('red');
});

// tslint:disable-next-line: max-classes-per-file
@smartdata.Collection(() => {
  return testDb;
})
class Truck extends smartdata.SmartDataDbDoc<Car, Car> {
  @smartdata.unI()
  public id: string = smartunique.shortId();

  @smartdata.svDb()
  public color: string;

  @smartdata.svDb()
  public brand: string;

  constructor(colorArg: string, brandArg: string) {
    super();
    this.color = colorArg;
    this.brand = brandArg;
  }
}

tap.test('should store a new Truck', async () => {
  const truck = new Truck('blue', 'MAN');
  await truck.save();
  const myTruck2 = await Truck.getInstance({ color: 'blue' });
  expect(myTruck2.color).toEqual('blue');
  myTruck2.color = 'red';
  await myTruck2.save();
  const myTruck3 = await Truck.getInstance({ color: 'blue' });
  expect(myTruck3).toBeNull();
});

tap.test('should return a count', async () => {
  const truckCount = await Truck.getCount();
  expect(truckCount).toEqual(1);
});

tap.test('should use a cursor', async () => {
  const cursor = await Car.getCursor({});
  let counter = 0;
  await cursor.forEach(async (carArg) => {
    counter++;
    counter % 50 === 0 ? console.log(`50 more of ${carArg.color}`) : null;
  });
});

// =======================================
// close the database connection
// =======================================
tap.test('close', async () => {
  if (smartmongoInstance) {
    await smartmongoInstance.stopAndDumpToDir('./.nogit/dbdump/test.ts');
  } else {
    await testDb.mongoDb.dropDatabase();
    await testDb.close();
  }
  setTimeout(() => process.exit(), 2000);
});

tap.start({ throwOnError: true });