import { deepStrictEqual, strictEqual, rejects } from 'assert';
import { Database } from '../source/database/database.js';
import currency from '../source/database/currency.js';
import { User } from '../source/database/user.js';
import { Group } from '../source/database/group.js';
import { Expense } from '../source/database/expense.js';
import { ManageGroup } from '../source/core/manageGroup.js';

describe('Database', () => {
  const db = new Database();

  before(async () => connectDatabaseAndCreateSchema(db));

  const user = new User('user', 'x', new Date(), 1);
  const group = new Group('myGroup', currency.swissFrancs, user.getWithoutPassword(), 'd', false, 1);
  const expense = new Expense(group, user.getWithoutPassword(), 'd', 500, new Date(), 1);

  it('stores a user', async () => {
    await db.createUser(user);

    deepStrictEqual(await db.getUser(user.name), user.getWithoutPassword());
    deepStrictEqual(await db.getUserWithPassword(user.name), user);
  });

  it('stores a new group', async () => {
    await db.createGroup(group);
    const storedGroup = await db.getGroup(group.name);
    deepStrictEqual(group, storedGroup);

    const groups = await db.getGroupsOfUser(user);
    strictEqual(groups.length, 1);
    deepStrictEqual(groups[0], group);

    const usersOfGroup = await db.getUsersOfGroup(group);
    strictEqual(usersOfGroup.length, 1);
    deepStrictEqual(usersOfGroup[0], user.getWithoutPassword());
  });

  it('creates a new expense', async () => {
    await db.createExpense(expense);
    const storedExpenses = await db.getExpensesOfGroup(group);

    strictEqual(storedExpenses.length, 1);
    deepStrictEqual(storedExpenses[0], expense);
  });

  it('deletes an expense', async () => {
    await db.deleteExpense(expense.id);
    const expenses = await db.getExpensesOfGroup(group);
    strictEqual(expenses.length, 0);
  })

  after(async () => await resetAndDisconnectDatabase(db));
});

describe('Settlement', () => {
  let db, manageGroup;

  const user1 = new User('1', '', new Date(), 1);
  const user2 = new User('2', '', new Date(), 2);
  const user3 = new User('3', '', new Date(), 3);
  const user4 = new User('4', '', new Date(), 4);
  const group = new Group('g', currency.euros, user1, '', false, 1);

  beforeEach(async () => {
    db = new Database();
    await connectDatabaseAndCreateSchema(db);
    await db.createUser(user1);
    await db.createGroup(group);
    manageGroup = new ManageGroup(group.name, db);
  });

  it('does not settle up open groups', async () => {
    await rejects(() => manageGroup.getSettlements(), { message: 'Group not closed' });
  });

  it('has no settlements when there are no expenses', async () => {
    await manageGroup.closeGroup();
    deepStrictEqual(await manageGroup.getSettlements(), []);
  });

  it('has no settlements in a one-man group', async () => {
    await manageGroup.addExpense(user1, '', 100);
    await manageGroup.closeGroup();
    deepStrictEqual(await manageGroup.getSettlements(), []);
  });

  it('splits equally between two people', async () => {
    await addUsers(user2);
    await manageGroup.addExpense(user1, '', 60);
    await manageGroup.addExpense(user1, '', 40);
    await manageGroup.closeGroup();

    deepStrictEqual(await manageGroup.getSettlements(), [{ from: '2', to: '1', amount: 50 }]);
  });

  it('splits equally between three people, with one expense', async () => {
    await addUsers(user2, user3);
    await manageGroup.addExpense(user1, '1', 100);
    await manageGroup.closeGroup();

    deepStrictEqual(await manageGroup.getSettlements(), [
      { from: '2', to: '1', amount: 33 },
      { from: '3', to: '1', amount: 33 }
    ]);
  });

  it('splits equally between three people, with two expenses', async () => {
    await addUsers(user2, user3);
    await manageGroup.addExpense(user1, '', 100);
    await manageGroup.addExpense(user2, '', 110);
    await manageGroup.closeGroup();

    deepStrictEqual(await manageGroup.getSettlements(), [
      { from: '3', to: '1', amount: 30 },
      { from: '3', to: '2', amount: 40 }
    ]);
  });

  it('splits equally between four people', async () => {
    await addUsers(user2, user3, user4);
    await manageGroup.addExpense(user1, '', 14);
    await manageGroup.addExpense(user2, '', 41);
    await manageGroup.addExpense(user3, '', 29);
    await manageGroup.closeGroup();

    deepStrictEqual(await manageGroup.getSettlements(), [
      { from: '1', to: '2', amount: 7 },
      { from: '4', to: '2', amount: 13 },
      { from: '4', to: '3', amount: 8 }
    ]);
  });

  async function addUsers(...users) {
    for (const user of users) {
      await db.createUser(user);
      await manageGroup.addUser(user.name);
    }
  }

  afterEach(async () => await resetAndDisconnectDatabase(db));
});

async function connectDatabaseAndCreateSchema(db) {
  await db.connect('../database/main.sql');
}

async function resetAndDisconnectDatabase(db) {
  await db.query('DROP SCHEMA public CASCADE; CREATE SCHEMA public;');
  await db.disconnect();
}
