import couchService from '@/services/CouchService'
import ITransaction from '@/models/ITransaction'
import exchangeService from '@/services/ExchangeService'
import TransactionTag from '@/enums/TransactionTag'
import TransactionType from '@/enums/TransactionType'
import ISplit from '@/models/ISplit'
import IUser from '@/models/IUser'
import IAccount from '@/models/IAccount'
import IEncryptable from '@/models/IEncryptable'
import IEncrypted from '@/models/IEncrypted'

class TransactionService {
  public async getNew(
    user: IUser | null,
    account: IAccount
  ): Promise<ITransaction> {
    const current: IUser | undefined = account.users.find(
      (u: IUser) => !!(user && u.email === user.email)
    )
    const transaction: ITransaction = {
      name: '',
      accountId: account._id || '',
      date: new Date(),
      amount: 0,
      payBy: current ? current.alias || '' : '',
      payFor: account.users.map((u: IUser) => ({
        alias: u.alias || '',
        weight: 1
      })),
      mainCurrency: account.mainCurrency,
      currencies: account.currencies,
      userIds: [],
      doctype: 'transaction',
      tag: TransactionTag.None,
      transactionType: TransactionType.normal,
      exchange: null,
      location: {
        lat: 0,
        lon: 0,
        place: ''
      }
    }
    return transaction
  }

  public getMultiply(transaction: ITransaction, user: string): number {
    const userSplit: ISplit | undefined = transaction.payFor.find(
      (p) => p.alias === user
    )
    return userSplit ? userSplit.weight : 1
  }

  public async get(id: string): Promise<ITransaction | null> {
    const transaction: ITransaction = await couchService.get(id)
    return await this.decrypt(transaction)
  }

  public async add(
    accountId: string,
    transaction: ITransaction
  ): Promise<PouchDB.Core.Response> {
    try {
      transaction._id = couchService.newId(`tra-${accountId}`, true)
      transaction.doctype = 'transaction'
      return await couchService.save(transaction)
    } catch (error) {
      // tslint:disable-next-line:no-console
      console.warn({ error })
      return {
        ok: false,
        id: '',
        rev: ''
      }
    }
  }

  public async save(transaction: ITransaction): Promise<PouchDB.Core.Response> {
    return await couchService.save(transaction)
  }

  public async multipleSave(
    transactions: ITransaction[]
  ): Promise<Array<PouchDB.Core.Response | PouchDB.Core.Error>> {
    return await couchService.multipleSave(transactions)
  }

  public async getAllByAccountId(accountId: string): Promise<ITransaction[]> {
    const response: PouchDB.Core.AllDocsResponse<
      ITransaction
    > = await couchService.getByPrefix<ITransaction>(`tra-${accountId}`)
    const transactions = response.rows.map((row) => row.doc) as ITransaction[]
    return transactions.sort((a: ITransaction, b: ITransaction) =>
      a.date <= b.date ? -1 : 1
    )
  }

  public getTotalCost(
    transactions: ITransaction[],
    currency: string,
    user: string | null = null
  ): number {
    let n: number = 0
    const reduceFn = (value: number, transaction: ITransaction) => {
      const amount = exchangeService.getAmountInBase(
        transaction.amount,
        currency,
        transaction.exchange
      )
      return this.round(value + amount)
    }
    if (user) {
      const plus: number = transactions
        .filter((transaction: ITransaction) => transaction.payBy === user)
        .reduce(reduceFn, 0)
      const minus = transactions
        .filter(
          (transaction: ITransaction) =>
            transaction.transactionType === TransactionType.refund &&
            transaction.payFor.map((p: ISplit) => p.alias).includes(user)
        )
        .reduce(reduceFn, n)
      n = Math.abs(plus - minus)
    } else {
      n = transactions
        .filter(
          (transaction: ITransaction) =>
            transaction.transactionType === TransactionType.normal
        )
        .reduce(reduceFn, 0)
    }
    return this.round(n)
  }

  public async remove(id: string): Promise<boolean> {
    return await couchService.remove(id)
  }

  private round(n: number): number {
    return Math.round(n * 100) / 100
  }

  private toEncrypt(transaction: ITransaction): IEncryptable | IEncrypted {
    if (!transaction) {
      return {}
    }
    return {
      name: transaction.name,
      date: transaction.date,
      amount: transaction.amount,
      payBy: transaction.payBy,
      tag: transaction.tag
    }
  }

  private async decrypt(transaction: ITransaction): Promise<ITransaction> {
    if (!transaction) {
      return transaction
    }
    const decrypt = await couchService.decrypt(
      transaction.accountId || '',
      this.toEncrypt(transaction)
    )
    return {
      ...transaction,
      ...decrypt
    }
  }
}

export default new TransactionService()
