import test from 'tape';
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
import { ModelNameEnum } from 'models/types';
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
import { Payment } from '../Payment/Payment';
import { PaymentTypeEnum } from '../Payment/types';
import {
  assertDoesNotThrow,
  assertThrows,
} from 'backend/database/tests/helpers';
import { PurchaseInvoice } from '../PurchaseInvoice/PurchaseInvoice';

const fyo = getTestFyo();
setupTestFyo(fyo, __filename);

const itemData = {
  name: 'Pen',
  rate: 100,
  unit: 'Unit',
  for: 'Both',
  trackItem: true,
  hasBatch: true,
  hasSerialNumber: true,
};

const partyData = {
  name: 'John Whoe',
  email: 'john@whoe.com',
};

const batchMap = {
  batchOne: {
    name: 'PN-AB001',
    manufactureDate: '2022-11-03T09:57:04.528',
  },
  batchTwo: {
    name: 'PN-AB002',
    manufactureDate: '2022-10-03T09:57:04.528',
  },
};

test('create test docs', async (t) => {
  await fyo.doc.getNewDoc(ModelNameEnum.Item, itemData).sync();

  t.ok(
    fyo.db.exists(ModelNameEnum.Item, itemData.name),
    `dummy item ${itemData.name}  exists`
  );

  await fyo.doc.getNewDoc(ModelNameEnum.Party, partyData).sync();
  t.ok(
    fyo.db.exists(ModelNameEnum.Party, partyData.name),
    `dummy party ${partyData.name} exists`
  );

  for (const batch of Object.values(batchMap)) {
    await fyo.doc.getNewDoc(ModelNameEnum.Batch, batch).sync(),
      t.ok(
        fyo.db.exists(ModelNameEnum.Batch, batch.name),
        `batch ${batch.name} exists`
      );
  }
});

test('create SINV with batch then create payment against it', async (t) => {
  const sinvDoc = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
    account: 'Debtors',
    party: partyData.name,
    items: [
      {
        item: itemData.name,
        batch: batchMap.batchOne.name,
        rate: itemData.rate,
        quantity: 2,
      },
    ],
  }) as SalesInvoice;

  await sinvDoc.sync();
  await sinvDoc.runFormulas();
  await sinvDoc.submit();

  t.ok(
    fyo.db.exists(ModelNameEnum.SalesInvoice, sinvDoc.name),
    `${sinvDoc.name} exists`
  );

  const paymentDoc = sinvDoc.getPayment();
  await paymentDoc?.sync();
  await paymentDoc?.submit();

  t.equals(paymentDoc?.name, 'PAY-1001');
});

test('create SINV return for one qty', async (t) => {
  const sinvDoc = (await fyo.doc.getDoc(
    ModelNameEnum.SalesInvoice,
    'SINV-1001'
  )) as SalesInvoice;

  let returnDoc = (await sinvDoc?.getReturnDoc()) as SalesInvoice;

  returnDoc.items = [];
  returnDoc.append('items', {
    item: itemData.name,
    batch: batchMap.batchOne.name,
    quantity: 1,
    rate: itemData.rate,
  });

  await returnDoc.runFormulas();
  await returnDoc.sync();
  await returnDoc.submit();

  t.ok(
    await fyo.db.exists(ModelNameEnum.SalesInvoice, returnDoc.name),
    'SINV return for one qty created'
  );

  t.equals(
    returnDoc.outstandingAmount?.float,
    itemData.rate,
    'returnDoc outstanding amount matches'
  );

  const returnSinvAles = await fyo.db.getAllRaw(
    ModelNameEnum.AccountingLedgerEntry,
    {
      fields: ['name', 'account', 'credit', 'debit'],
      filters: { referenceName: returnDoc.name! },
    }
  );

  for (const ale of returnSinvAles) {
    if (ale.account === 'Sales') {
      t.equal(
        fyo.pesa(ale.debit as string).float,
        fyo.pesa(itemData.rate).float,
        `return Invoice debited from ${ale.account}`
      );
    }

    if (ale.account === 'Debtors') {
      t.equal(
        fyo.pesa(ale.credit as string).float,
        fyo.pesa(itemData.rate).float,
        `return Invoice credited to ${ale.account}`
      );
    }
  }

  await assertThrows(
    async () => await sinvDoc.cancel(),
    'can not cancel a SINV when a return invoice is created against it'
  );
});

test('create SINV return for balance qty', async (t) => {
  const sinvDoc = (await fyo.doc.getDoc(
    ModelNameEnum.SalesInvoice,
    'SINV-1001'
  )) as SalesInvoice;

  const returnDoc = (await sinvDoc?.getReturnDoc()) as SalesInvoice;
  t.equals(
    Object.values(returnDoc.items!)[0].quantity,
    -1,
    'return doc has 1 qty left to return'
  );

  await returnDoc.sync();

  await returnDoc.runFormulas();
  await returnDoc.submit();

  t.ok(
    await fyo.db.exists(ModelNameEnum.SalesInvoice, returnDoc.name),
    'SINV return for one qty created'
  );

  t.equals(
    returnDoc.outstandingAmount?.float,
    -itemData.rate,
    'return doc outstanding amount matches'
  );
});

test('create payment for return invoice', async (t) => {
  const returnDoc = (await fyo.doc.getDoc(
    ModelNameEnum.SalesInvoice,
    'SINV-1002'
  )) as SalesInvoice;

  t.equals(returnDoc.returnAgainst, 'SINV-1001');

  const paymentDoc = returnDoc.getPayment() as Payment;
  t.equals(paymentDoc.paymentType, PaymentTypeEnum.Pay, 'payment type is pay');

  t.equals(
    paymentDoc.amount?.float,
    itemData.rate,
    'payment amount for return invoice matches'
  );

  await paymentDoc.sync();

  t.ok(
    await fyo.db.exists(ModelNameEnum.Payment, paymentDoc.name),
    'payment entry created for return invoice'
  );

  await assertDoesNotThrow(
    async () => await returnDoc.cancel(),
    'return invoice cancelled'
  );
});

test('creating PINV return when invoice is not paid', async (t) => {
  const pinvDoc = fyo.doc.getNewDoc(
    ModelNameEnum.PurchaseInvoice
  ) as PurchaseInvoice;

  await pinvDoc.set({
    party: partyData.name,
    account: 'Creditors',
    items: [
      {
        item: itemData.name,
        batch: batchMap.batchOne.name,
        quantity: 2,
        rate: itemData.rate,
      },
    ],
  });
  await pinvDoc.sync();
  await pinvDoc.submit();

  t.equals(pinvDoc.name, 'PINV-1001', `${pinvDoc.name} is submitted`);

  const returnDoc = (await pinvDoc.getReturnDoc()) as PurchaseInvoice;
  await returnDoc.sync();
  await returnDoc.submit();

  t.equals(
    returnDoc?.returnAgainst,
    pinvDoc.name,
    `return pinv created against ${pinvDoc.name}`
  );
  t.equals(
    Object.values(returnDoc.items!)[0].quantity,
    -2,
    'pinv returned qty matches'
  );

  const returnSinvAles = await fyo.db.getAllRaw(
    ModelNameEnum.AccountingLedgerEntry,
    {
      fields: ['name', 'account', 'credit', 'debit'],
      filters: { referenceName: returnDoc.name! },
    }
  );

  for (const ale of returnSinvAles) {
    if (ale.account === 'Creditors') {
      t.equal(
        fyo.pesa(ale.debit as string).float,
        returnDoc.outstandingAmount!.float,
        `return Invoice debited from ${ale.account}`
      );
    }

    if (ale.account === 'Cost of Goods Sold') {
      t.equal(
        fyo.pesa(ale.credit as string).float,
        returnDoc.outstandingAmount!.float,
        `return Invoice credited to ${ale.account}`
      );
    }
  }
});

closeTestFyo(fyo, __filename);
