import {
  request,
  expect,
  create,
  login,
} from '~/testInit';
import AccountTransaction from '@/models/AccountTransaction';
import Expense from '@/models/Expense';
import ResourceFieldMetadata from '@/models/ResourceFieldMetadata';

let loginRes;
let expenseType;
let cashType;
let expenseAccount;
let cashAccount;

describe('routes: /expenses/', () => {
  beforeEach(async () => {
    loginRes = await login();

    expenseType = await create('account_type', { normal: 'debit' });
    cashType = await create('account_type', { normal: 'debit' });

    expenseAccount = await create('account', { account_type_id: expenseType.id });
    cashAccount = await create('account', { account_type_id: cashType.id });
  });
  afterEach(() => {
    loginRes = null;
  });

  describe('POST: `/expenses`', () => {
    it('Should response unauthorized in case user was not authorized.', async () => {
      const res = await request().post('/api/expenses').send();

      expect(res.status).equals(401);
      expect(res.body.message).equals('unauthorized');
    });

    it('Should `payment_account_id` be required.', async () => {
      const res = await request().post('/api/expenses')
        .set('x-access-token', loginRes.body.token)
        .send();

      expect(res.body.errors).include.something.that.deep.equals({
        msg: 'Invalid value',
        param: 'payment_account_id',
        location: 'body',
      });
    });

    it('Should `expense_account_id` be required.', async () => {
      const res = await request().post('/api/expenses')
        .set('x-access-token', loginRes.body.token)
        .send();

      expect(res.body.errors).include.something.that.deep.equals({
        msg: 'Invalid value',
        param: 'expense_account_id',
        location: 'body',
      });
    });

    it('Should `amount` be required.', async () => {
      const res = await request().post('/api/expenses')
        .set('x-access-token', loginRes.body.token)
        .send();

      expect(res.body.errors).include.something.that.deep.equals({
        msg: 'Invalid value',
        param: 'amount',
        location: 'body',
      });
    });

    it('Should `exchange_rate` be required in case `currency_code` not equal default one.', () => {

    });

    it('Should response bad request in case expense account was not found.', async () => {
      const res = await request().post('/api/expenses')
        .set('x-access-token', loginRes.body.token)
        .send({
          expense_account_id: 100,
          payment_account_id: 100,
          amount: 100,
        });

      expect(res.body.errors).include.something.that.deep.equals({
        type: 'EXPENSE.ACCOUNT.NOT.FOUND', code: 200,
      });
    });

    it('Should response bad request in case payment account was not found.', async () => {
      const res = await request().post('/api/expenses')
        .set('x-access-token', loginRes.body.token)
        .send({
          expense_account_id: 100,
          payment_account_id: 100,
          amount: 100,
        });

      expect(res.body.errors).include.something.that.deep.equals({
        type: 'PAYMENT.ACCOUNT.NOT.FOUND', code: 100,
      });
    });

    it('Should response success with valid required data.', async () => {
      const res = await request().post('/api/expenses')
        .set('x-access-token', loginRes.body.token)
        .send({
          expense_account_id: expenseAccount.id,
          payment_account_id: cashAccount.id,
          amount: 100,
        });

      expect(res.status).equals(200);
    });

    it('Should record journal entries of expense transaction.', async () => {
      const res = await request()
        .post('/api/expenses')
        .set('x-access-token', loginRes.body.token)
        .send({
          expense_account_id: expenseAccount.id,
          payment_account_id: cashAccount.id,
          amount: 100,
        });

      const expensesEntries = await AccountTransaction.query()
        .where('reference_type', 'Expense')
        .where('reference_id', res.body.id);

      expect(expensesEntries.length).equals(2);
    });

    it('Should save expense transaction to the storage.', async () => {
      const res = await request()
        .post('/api/expenses')
        .set('x-access-token', loginRes.body.token)
        .send({
          expense_account_id: expenseAccount.id,
          payment_account_id: cashAccount.id,
          amount: 100,
        });

      const expenseTransaction = await Expense.query().where('id', res.body.id);
      expect(expenseTransaction.amount).equals(100);
    });

    it('Should response bad request in case custom field slug was not exists in the storage.', async () => {
      const res = await request()
        .post('/api/expenses')
        .set('x-access-token', loginRes.body.token)
        .send({
          expense_account_id: expenseAccount.id,
          payment_account_id: cashAccount.id,
          amount: 100,
          custom_options: [
            {
              key: 'random_key',
              value: 'Value here',
            },
          ],
        });

      expect(res.status).equals(400);
    });

    it('Should save expense custom fields to the storage.', async () => {
      const resource = await create('resource', { name: 'Expense' });
      const resourceField = await create('resource_field', {
        resource_id: resource.id,
        slug: 'custom_field_1',
      });

      const res = await request()
        .post('/api/expenses')
        .set('x-access-token', loginRes.body.token)
        .send({
          expense_account_id: expenseAccount.id,
          payment_account_id: cashAccount.id,
          amount: 100,
          custom_fields: [
            {
              key: 'custom_field_1',
              value: 'Value here',
            },
          ],
        });
       
      const storedResourceItemMetadata = await ResourceFieldMetadata.query()
        .where('resource_id', resource.id)
        .where('resource_item_id', res.body.id);

      expect(storedResourceItemMetadata.metadata.length).equals(1);
      expect(storedResourceItemMetadata.metadata[0].resourceId).equals(resource.id);
      expect(storedResourceItemMetadata.metadata[0].resourceItemId).equals(res.body.id);

      expect(storedResourceItemMetadata.metadata[0].key).equals('custom_field_1');
      expect(storedResourceItemMetadata.metadata[0].value).equals('Value here');
    });
  });

  describe('POST: `/expenses/:id`', () => {
    it('Should response unauthorized in case user was not authorized.', () => {

    });

    it('Should response not found in case expense was not exist.', () => {

    });

    it('Should update the expense transaction.', () => {

    });
  });

  describe('DELETE: `/expenses/:id`', () => {
    it('Should response not found in case expense not found.', async () => {
      const res = await request()
        .delete('/api/expenses/1000')
        .set('x-access-token', loginRes.body.token)
        .send();

      expect(res.body.errors).include.something.that.deep.equals({
        type: 'EXPENSE.TRANSACTION.NOT.FOUND', code: 100,
      });
    });

    it('Should response success in case expense transaction was exist.', async () => {
      const expense = await create('expense');
      const res = await request()
        .delete(`/api/expenses/${expense.id}`)
        .set('x-access-token', loginRes.body.token)
        .send();

      expect(res.status).equals(200);
    });

    it('Should delete the expense transaction from the storage.', async () => {
      const expense = await create('expense');
      await request()
        .delete(`/api/expenses/${expense.id}`)
        .set('x-access-token', loginRes.body.token)
        .send();

      const storedExpense = await Expense.query().findById(expense.id);
      expect(storedExpense).equals(undefined);
    });

    it('Should delete the journal entries that associated to expense transaction from the storage.', async () => { 
      const expense = await create('expense');
      await request()
        .delete(`/api/expense/${expense.id}`)
        .set('x-access-token', loginRes.body.token)
        .send();

      const expenseEntries = await AccountTransaction.query()
        .where('reference_type', 'Expense')
        .where('reference_id', expense.id);

      expect(expenseEntries.length).equals(0);
    });

    it('Should reverse accounts balance that associated to expense transaction.', () => { 

    });

    it('Should delete the custom fields that associated to resource and resource item.', () => {

    });
  });

  describe('POST: `/expenses/bulk`', () => {
    it('Should response unauthorized in case user was not authorized.', async () => {
      const res = await request().post('/api/expenses/bluk').send();

      expect(res.status).equals(401);
      expect(res.body.message).equals('unauthorized');
    });

    it('Should response bad request in case expenses was not array.', async () => {
      const res = await request()
        .post('/api/expenses/bulk')
        .set('x-access-token', loginRes.body.token)
        .send({
          expenses: 'Not array :(',
        });

      expect(res.body.errors).include.something.that.deep.equals({
        value: 'Not array :(',
        msg: 'Invalid value',
        param: 'expenses',
        location: 'body',
      });
    });

    it('Should response bad request in case one expense account was not found.', async () => {
      const res = await request()
        .post('/api/expenses/bulk')
        .set('x-access-token', loginRes.body.token)
        .send({
          expenses: [
            {
              payment_account_id: 100,
              expense_account_id: 100,
              amount: 1000,
            },
          ],
        });
      expect(res.body.reasons).include.something.that.deep.equals({
        type: 'EXPENSE.ACCOUNTS.NOT.FOUND', code: 200, accounts: [100],
      });
    });

    it('Should response bad request in case one of payment account was not found.', async () => {
      const res = await request()
        .post('/api/expenses/bulk')
        .set('x-access-token', loginRes.body.token)
        .send({
          expenses: [
            {
              payment_account_id: 100,
              expense_account_id: 100,
              amount: 1000,
            },
          ],
        });
      expect(res.body.reasons).include.something.that.deep.equals({
        type: 'PAYMENY.ACCOUNTS.NOT.FOUND', code: 100, accounts: [100],
      });
    });

    it('Should store expenses transactions to the storage.', async () => {
      const res = await request()
        .post('/api/expenses/bulk')
        .set('x-access-token', loginRes.body.token)
        .send({
          expenses: [
            {
              payment_account_id: cashAccount.id,
              expense_account_id: expenseAccount.id,
              amount: 1000,
            },
            {
              payment_account_id: cashAccount.id,
              expense_account_id: expenseAccount.id,
              amount: 1000,
            },
          ],
        });

      const expenseTransactions = await Expense.query();
      expect(expenseTransactions.length).equals(2);
    });

    it('Should store journal entries of expenses transactions to the storage.', async () => {
      const res = await request()
        .post('/api/expenses/bulk')
        .set('x-access-token', loginRes.body.token)
        .send({
          expenses: [
            {
              payment_account_id: cashAccount.id,
              expense_account_id: expenseAccount.id,
              amount: 1000,
            },
            {
              payment_account_id: cashAccount.id,
              expense_account_id: expenseAccount.id,
              amount: 1000,
            },
          ],
        });

      const expenseJournalEntries = await AccountTransaction.query();
      expect(expenseJournalEntries.length).equals(4);
    });
  });

  describe('POST: `/expenses/:id/publish`', () => {
    it('Should response not found in case the expense id was not exist.', async () => {
      const expense = await create('expense', { published: false });
      const res = await request()
        .post('/api/expenses/100/publish')
        .set('x-access-token', loginRes.body.token)
        .send();

      expect(res.status).equals(400);
      expect(res.body.errors).include.something.that.deep.equals({
        type: 'EXPENSE.NOT.FOUND', code: 100,
      });
    });

    it('Should response bad request in case expense is already published.', async () => {
      const expense = await create('expense', { published: true });
      const res = await request()
        .post(`/api/expenses/${expense.id}/publish`)
        .set('x-access-token', loginRes.body.token)
        .send();

      expect(res.status).equals(400);
      expect(res.body.errors).include.something.that.deep.equals({
        type: 'EXPENSE.ALREADY.PUBLISHED', code: 200,
      });
    });

    it('Should publish the expense transaction.', async () => {
      const expense = await create('expense', { published: false });
      const res = await request()
        .post(`/api/expenses/${expense.id}/publish`)
        .set('x-access-token', loginRes.body.token)
        .send();

      const storedExpense = await Expense.query().findById(expense.id);
      expect(storedExpense.published).equals(1);
    });

    it('Should publish the journal entries that associated to the given expense transaction.', async () => {
      const expense = await create('expense', { published: false });
      const transaction = await create('account_transaction', {
        reference_id: expense.id,
        reference_type: 'Expense',
      });
      const res = await request()
        .post(`/api/expenses/${expense.id}/publish`)
        .set('x-access-token', loginRes.body.token)
        .send();

      const entries = await AccountTransaction.query()
        .where('reference_id', expense.id)
        .where('reference_type', 'Expense');

      entries.forEach((entry) => {
        expect(entry.draft).equals(0);
      });
    });

    it('Should response success in case expense was exist and not published.', async () => {
      const expense = await create('expense', { published: false });
      const res = await request()
        .post(`/api/expenses/${expense.id}/publish`)
        .set('x-access-token', loginRes.body.token)
        .send();

      expect(res.status).equals(200);
    });
  });

  describe('GET: `/expenses/:id`', () => {
    it('Should response view not found in case the custom view id was not exist.', async () => {
      const expense = await create('expense');

      const res = await request()
        .get(`/api/expenses/${expense.id}123`)
        .set('x-access-token', loginRes.body.token)
        .send();

      expect(res.status).equals(404);
      expect(res.body.errors).include.something.deep.equals({
        type: 'EXPENSE.TRANSACTION.NOT.FOUND', code: 100,
      });
    });

    it('Should retrieve custom fields metadata.', async () => {
      const expense = await create('expense');
      const resource = await create('resource', { name: 'Expense' });
      const resourceField = await create('resource_field', {
        resource_id: resource.id,
        slug: 'custom_field_1',
      });

      const resourceFieldMetadata = await create('resource_custom_field_metadata', {
        resource_id: resource.id,
        resource_item_id: expense.id,
        key: 'custom_field_1',
      });

      const res = await request()
        .get(`/api/expenses/${expense.id}`)
        .set('x-access-token', loginRes.body.token)
        .send();

      expect(res.status).equals(200);

      expect(res.body.custom_fields.length).equals(1);
      expect(res.body.custom_fields[0].key).equals('custom_field_1');
      expect(res.body.custom_fields[0].value).equals(resourceFieldMetadata.value);
    });
  });
});
