<?php

/**
 * PeriodOverview.php
 * Copyright (c) 2019 james@firefly-iii.org
 *
 * This file is part of Firefly III (https://github.com/firefly-iii).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

declare(strict_types=1);

namespace FireflyIII\Support\Http\Controllers;

use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Category;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Navigation;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

/**
 * Trait PeriodOverview.
 *
 * TODO verify this all works as expected.
 *
 * - Always request start date and end date.
 * - Group expenses, income, etc. under this period.
 * - Returns collection of arrays. Fields
 *      title (string),
 *      route (string)
 *      total_transactions (int)
 *      spent (array),
 *      earned (array),
 *      transferred_away (array)
 *      transferred_in (array)
 *      transferred (array)
 *
 * each array has the following format:
 * currency_id => [
 *       currency_id : 1, (int)
 *       currency_symbol : X (str)
 *       currency_name: Euro (str)
 *       currency_code: EUR (str)
 *       amount: -1234 (str)
 *       count: 23
 *       ]
 */
trait PeriodOverview
{
    protected AccountRepositoryInterface $accountRepository;
    protected CategoryRepositoryInterface $categoryRepository;
    protected TagRepositoryInterface $tagRepository;
    protected JournalRepositoryInterface $journalRepos;
    protected PeriodStatisticRepositoryInterface $periodStatisticRepo;
    private Collection $statistics;
    private array $transactions;

    /**
     * This method returns "period entries", so nov-2015, dec-2015, etc. (this depends on the users session range)
     * and for each period, the amount of money spent and earned. This is a complex operation which is cached for
     * performance reasons.
     *
     * @throws FireflyException
     */
    protected function getAccountPeriodOverview(Account $account, Carbon $start, Carbon $end): array
    {
        Log::debug(sprintf('Now in getAccountPeriodOverview(#%d, %s %s)', $account->id, $start->format('Y-m-d H:i:s.u'), $end->format('Y-m-d H:i:s.u')));
        $this->accountRepository   = app(AccountRepositoryInterface::class);
        $this->accountRepository->setUser($account->user);
        $this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);
        $range                     = Navigation::getViewRange(true);
        [$start, $end]             = $end < $start ? [$end, $start] : [$start, $end];

        /** @var array $dates */
        $dates                     = Navigation::blockPeriods($start, $end, $range);
        [$start, $end]             = $this->getPeriodFromBlocks($dates, $start, $end);
        $this->statistics          = $this->periodStatisticRepo->allInRangeForModel($account, $start, $end);

        $entries                   = [];
        Log::debug(sprintf('Count of loops: %d', count($dates)));
        foreach ($dates as $currentDate) {
            $entries[] = $this->getSingleModelPeriod($account, $currentDate['period'], $currentDate['start'], $currentDate['end']);
        }
        Log::debug('End of getAccountPeriodOverview()');

        return $entries;
    }

    /**
     * Overview for single category. Has been refactored recently.
     *
     * @throws FireflyException
     */
    protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array
    {
        $this->categoryRepository  = app(CategoryRepositoryInterface::class);
        $this->categoryRepository->setUser($category->user);
        $this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);

        $range                     = Navigation::getViewRange(true);
        [$start, $end]             = $end < $start ? [$end, $start] : [$start, $end];

        /** @var array $dates */
        $dates                     = Navigation::blockPeriods($start, $end, $range);
        $entries                   = [];
        [$start, $end]             = $this->getPeriodFromBlocks($dates, $start, $end);
        $this->statistics          = $this->periodStatisticRepo->allInRangeForModel($category, $start, $end);

        Log::debug(sprintf('Count of loops: %d', count($dates)));
        foreach ($dates as $currentDate) {
            $entries[] = $this->getSingleModelPeriod($category, $currentDate['period'], $currentDate['start'], $currentDate['end']);
        }

        return $entries;
    }

    protected function getGenericPeriod(string $type, string $period, Carbon $start, Carbon $end): array
    {
        $return             = [
            'title'              => Navigation::periodShow($start, $period),
            'route'              => route('transactions.index', [$type, $start->format('Y-m-d'), $end->format('Y-m-d')]),
            'total_transactions' => 0,
        ];
        $setTypes           = [
            'withdrawal' => 'spent',
            'expenses'   => 'spent',
            'deposit'    => 'earned',
            'revenue'    => 'earned',
            'transfer'   => 'transferred',
            'transfers'  => 'transferred',
        ];
        if (!array_key_exists($type, $setTypes)) {
            throw new FireflyException(sprintf('[c] Cannot deal with type "%s"', $type));
        }
        $setType            = $setTypes[$type];

        $this->transactions = [];
        $set                = $this->getSingleGenericPeriodByType($start, $end, $type);
        $return['total_transactions'] += $set['count'];
        $return[$setType]   = $set;

        return $return;
    }

    /**
     * Same as above, but for lists that involve transactions without a budget.
     *
     * This method has been refactored recently.
     *
     * @throws FireflyException
     */
    protected function getNoModelPeriodOverview(string $model, Carbon $start, Carbon $end): array
    {
        Log::debug(sprintf('Now in getNoModelPeriodOverview(%s, %s %s)', $model, $start->format('Y-m-d'), $end->format('Y-m-d')));
        $this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);
        $range                     = Navigation::getViewRange(true);
        [$start, $end]             = $end < $start ? [$end, $start] : [$start, $end];

        /** @var array $dates */
        $dates                     = Navigation::blockPeriods($start, $end, $range);
        [$start, $end]             = $this->getPeriodFromBlocks($dates, $start, $end);
        $entries                   = [];
        $this->statistics          = $this->periodStatisticRepo->allInRangeForPrefix(sprintf('no_%s', $model), $start, $end);
        Log::debug(sprintf('Collected %d stats', $this->statistics->count()));

        foreach ($dates as $currentDate) {
            $entries[] = $this->getSingleNoModelPeriodOverview($model, $currentDate['start'], $currentDate['end'], $currentDate['period']);
        }

        return $entries;
    }

    protected function getSingleModelPeriod(Model $model, string $period, Carbon $start, Carbon $end): array
    {
        Log::debug(sprintf('Now in getSingleModelPeriod(%s #%d, %s %s)', $model::class, $model->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
        $types              = ['spent', 'earned', 'transferred_in', 'transferred_away'];
        $return             = [
            'title'              => Navigation::periodShow($start, $period),
            'route'              => route(sprintf('%s.show', strtolower(Str::plural(class_basename($model)))), [
                $model->id,
                $start->format('Y-m-d'),
                $end->format('Y-m-d'),
            ]),
            'total_transactions' => 0,
        ];
        $this->transactions = [];
        foreach ($types as $type) {
            $set           = $this->getSingleModelPeriodByType($model, $start, $end, $type);
            $return['total_transactions'] += $set['count'];
            unset($set['count']);
            $return[$type] = $set;
        }

        return $return;
    }

    /**
     * This shows a period overview for a tag. It goes back in time and lists all relevant transactions and sums.
     *
     * @throws FireflyException
     */
    protected function getTagPeriodOverview(Tag $tag, Carbon $start, Carbon $end): array
    { // period overview for tags.
        $this->tagRepository       = app(TagRepositoryInterface::class);
        $this->tagRepository->setUser($tag->user);
        $this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);

        $range                     = Navigation::getViewRange(true);
        [$start, $end]             = $end < $start ? [$end, $start] : [$start, $end];

        /** @var array $dates */
        $dates                     = Navigation::blockPeriods($start, $end, $range);
        $entries                   = [];
        [$start, $end]             = $this->getPeriodFromBlocks($dates, $start, $end);
        $this->statistics          = $this->periodStatisticRepo->allInRangeForModel($tag, $start, $end);

        Log::debug(sprintf('Count of loops: %d', count($dates)));
        foreach ($dates as $currentDate) {
            $entries[] = $this->getSingleModelPeriod($tag, $currentDate['period'], $currentDate['start'], $currentDate['end']);
        }

        return $entries;
    }

    /**
     * @throws FireflyException
     */
    protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array
    {
        $this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);
        $range                     = Navigation::getViewRange(true);
        [$start, $end]             = $end < $start ? [$end, $start] : [$start, $end];

        /** @var array $dates */
        $dates                     = Navigation::blockPeriods($start, $end, $range);
        $entries                   = [];
        $this->statistics          = $this->periodStatisticRepo->allInRangeForPrefix('all_', $start, $end);
        Log::debug(sprintf('Collected %d statistics', $this->statistics->count()));

        foreach ($dates as $currentDate) {
            $entries[] = $this->getGenericPeriod($transactionType, $currentDate['period'], $currentDate['start'], $currentDate['end']);
        }

        return $entries;
    }

    /**
     * Filter a list of journals by a set of dates, and then group them by currency.
     */
    private function filterJournalsByDate(array $array, Carbon $start, Carbon $end): array
    {
        $result = [];

        /** @var array $journal */
        foreach ($array as $journal) {
            if ($journal['date'] <= $end && $journal['date'] >= $start) {
                $result[] = $journal;
            }
        }

        return $result;
    }

    private function filterPrefixedStatistics(Carbon $start, Carbon $end, string $prefix): Collection
    {
        if (0 === $this->statistics->count()) {
            Log::debug('Have no statistic to filter!');

            return new Collection();
        }

        return $this->statistics->filter(
            static fn (PeriodStatistic $statistic): bool => (
                $statistic->start->eq($start)
                && $statistic->end->eq($end)
                && str_starts_with($statistic->type, $prefix)
            )
        );
    }

    private function filterStatistics(Carbon $start, Carbon $end, string $type): Collection
    {
        if (0 === $this->statistics->count()) {
            Log::debug('Have no statistic to filter!');

            return new Collection();
        }
        Log::debug(sprintf('Now in filterStatistics("%s")', $type));

        return $this->statistics->filter(static function (PeriodStatistic $statistic) use ($start, $end, $type): bool {
            return $statistic->start->isSameSecond($start) && $statistic->end->isSameSecond($end) && $statistic->type === $type;
        });
    }

    private function filterTransactionsByType(TransactionTypeEnum $type, Carbon $start, Carbon $end): array
    {
        $result = [];

        foreach ($this->transactions as $item) {
            $date = Carbon::parse($item['date']);
            $fits = $item['type'] === $type->value && $date >= $start && $date <= $end;
            if ($fits) {
                // if type is withdrawal, negative amount:
                if (TransactionTypeEnum::WITHDRAWAL === $type && 1 === bccomp((string) $item['amount'], '0')) {
                    $item['amount'] = Steam::negative($item['amount']);
                }
                $result[] = $item;
            }
        }

        return $result;
    }

    private function filterTransfers(string $direction, Carbon $start, Carbon $end): array
    {
        $result = [];

        foreach ($this->transactions as $item) {
            $date = Carbon::parse($item['date']);
            if ($date >= $start && $date <= $end) {
                if ('Transfer' === $item['type'] && 'away' === $direction && -1 === bccomp((string) $item['amount'], '0')) {
                    $result[] = $item;

                    continue;
                }
                if ('Transfer' === $item['type'] && 'in' === $direction && 1 === bccomp((string) $item['amount'], '0')) {
                    $result[] = $item;
                }
            }
        }

        return $result;
    }

    private function getPeriodFromBlocks(array $dates, Carbon $start, Carbon $end): array
    {
        Log::debug('Filter generated periods to select the oldest and newest date.');
        foreach ($dates as $row) {
            $currentStart = clone $row['start'];
            $currentEnd   = clone $row['end'];
            if ($currentStart->lt($start)) {
                Log::debug(sprintf('New start: was %s, now %s', $start->format('Y-m-d'), $currentStart->format('Y-m-d')));
                $start = $currentStart;
            }
            if ($currentEnd->gt($end)) {
                Log::debug(sprintf('New end: was %s, now %s', $end->format('Y-m-d'), $currentEnd->format('Y-m-d')));
                $end = $currentEnd;
            }
        }

        return [$start, $end];
    }

    private function getSingleGenericPeriodByType(Carbon $start, Carbon $end, string $type): array
    {
        $filterType = sprintf('all_%s', $type);
        $statistics = $this->filterStatistics($start, $end, $filterType);
        $types      = config(sprintf('firefly.transactionTypesByType.%s', $type));
        // nothing found, regenerate them.
        if (0 === $statistics->count()) {
            if (0 === count($this->transactions)) {
                // get collection!
                // collect all journals in this period (regardless of type)
                $collector          = app(GroupCollectorInterface::class);
                $collector->setTypes($types)->setRange($start, $end);
                $this->transactions = $collector->getExtractedJournals();
                Log::debug(sprintf('Going to group %d found journal(s)', count($types)));
            }

            $grouped = $this->groupByCurrency($this->filterJournalsByDate($this->transactions, $start, $end));
            $this->saveGroupedForPrefix('all', $start, $end, $type, $grouped);

            return $grouped;
        }
        $grouped    = ['count' => 0];

        /** @var PeriodStatistic $statistic */
        foreach ($statistics as $statistic) {
            $id           = (int) $statistic->transaction_currency_id;
            $currency     = Amount::getTransactionCurrencyById($id);
            $grouped[$id] = [
                'amount'                  => (string) $statistic->amount,
                'count'                   => (int) $statistic->count,
                'currency_id'             => $currency->id,
                'currency_name'           => $currency->name,
                'currency_code'           => $currency->code,
                'currency_symbol'         => $currency->symbol,
                'currency_decimal_places' => $currency->decimal_places,
            ];
            $grouped['count'] += (int) $statistic->count;
        }

        return $grouped;
    }

    private function getSingleModelPeriodByType(Model $model, Carbon $start, Carbon $end, string $type): array
    {
        Log::debug(sprintf(
            'Now in getSingleModelPeriodByType(%s #%d, %s %s, %s)',
            $model::class,
            $model->id,
            $start->format('Y-m-d H:i:s.u'),
            $end->format('Y-m-d H:i:s.u'),
            $type
        ));
        $statistics = $this->filterStatistics($start, $end, $type);

        // nothing found, regenerate them.
        if (0 === $statistics->count()) {
            Log::debug(sprintf('Found nothing between %s and %s for type "%s"', $start->format('Y-m-d H:i:s.u'), $end->format('Y-m-d H:i:s.u'), $type));
            if (0 === count($this->transactions)) {
                switch ($model::class) {
                    default:
                        throw new FireflyException(sprintf('Cannot deal with model of type "%s"', $model::class));

                    case Category::class:
                        $this->transactions = $this->categoryRepository->periodCollection($model, $start, $end);

                        break;

                    case Account::class:
                        $this->transactions = $this->accountRepository->periodCollection($model, $start, $end);

                        break;

                    case Tag::class:
                        $this->transactions = $this->tagRepository->periodCollection($model, $start, $end);

                        break;
                }
            }

            switch ($type) {
                default:
                    throw new FireflyException(sprintf('Cannot deal with type %s', $type));

                case 'spent':
                    $result = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $start, $end);

                    break;

                case 'earned':
                    $result = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $start, $end);

                    break;

                case 'transferred_in':
                    $result = $this->filterTransfers('in', $start, $end);

                    break;

                case 'transferred_away':
                    $result = $this->filterTransfers('away', $start, $end);

                    break;
            }
            // each result must be grouped by currency, then saved as period statistic.
            Log::debug(sprintf('Going to group %d found journal(s)', count($result)));
            $grouped = $this->groupByCurrency($result);

            $this->saveGroupedAsStatistics($model, $start, $end, $type, $grouped);

            return $grouped;
        }
        $grouped    = ['count' => 0];

        /** @var PeriodStatistic $statistic */
        foreach ($statistics as $statistic) {
            $id           = (int) $statistic->transaction_currency_id;
            $currency     = Amount::getTransactionCurrencyById($id);
            $grouped[$id] = [
                'amount'                  => (string) $statistic->amount,
                'count'                   => (int) $statistic->count,
                'currency_id'             => $currency->id,
                'currency_name'           => $currency->name,
                'currency_code'           => $currency->code,
                'currency_symbol'         => $currency->symbol,
                'currency_decimal_places' => $currency->decimal_places,
            ];
            $grouped['count'] += (int) $statistic->count;
        }

        return $grouped;
    }

    private function getSingleNoModelPeriodOverview(string $model, Carbon $start, Carbon $end, string $period): array
    {
        Log::debug(sprintf('getSingleNoModelPeriodOverview(%s, %s, %s, %s)', $model, $start->format('Y-m-d'), $end->format('Y-m-d'), $period));
        $statistics = $this->filterPrefixedStatistics($start, $end, sprintf('no_%s', $model));
        $title      = Navigation::periodShow($end, $period);

        if (0 === $statistics->count()) {
            Log::debug(sprintf('Found no statistics in period %s - %s, regenerating them.', $start->format('Y-m-d'), $end->format('Y-m-d')));

            switch ($model) {
                default:
                    throw new FireflyException(sprintf('[b] Cannot deal with model of type "%s"', $model));

                case 'budget':
                    // get all expenses without a budget.
                    /** @var GroupCollectorInterface $collector */
                    $collector   = app(GroupCollectorInterface::class);
                    $collector->setRange($start, $end)->withoutBudget()->withAccountInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
                    $spent       = $collector->getExtractedJournals();
                    $earned      = [];
                    $transferred = [];

                    break;

                case 'category':
                    // collect all expenses in this period:
                    /** @var GroupCollectorInterface $collector */
                    $collector   = app(GroupCollectorInterface::class);
                    $collector->withoutCategory();
                    $collector->setRange($start, $end);
                    $collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
                    $earned      = $collector->getExtractedJournals();

                    // collect all income in this period:
                    /** @var GroupCollectorInterface $collector */
                    $collector   = app(GroupCollectorInterface::class);
                    $collector->withoutCategory();
                    $collector->setRange($start, $end);
                    $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
                    $spent       = $collector->getExtractedJournals();

                    // collect all transfers in this period:
                    /** @var GroupCollectorInterface $collector */
                    $collector   = app(GroupCollectorInterface::class);
                    $collector->withoutCategory();
                    $collector->setRange($start, $end);
                    $collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
                    $transferred = $collector->getExtractedJournals();

                    break;
            }
            $groupedSpent       = $this->groupByCurrency($spent);
            $groupedEarned      = $this->groupByCurrency($earned);
            $groupedTransferred = $this->groupByCurrency($transferred);
            $entry              = ['title' => $title, 'route' => route(sprintf('%s.no-%s', Str::plural($model), $model), [
                $start->format('Y-m-d'),
                $end->format('Y-m-d'),
            ]), 'total_transactions' => count($spent), 'spent' => $groupedSpent, 'earned' => $groupedEarned, 'transferred' => $groupedTransferred];
            $this->saveGroupedForPrefix(sprintf('no_%s', $model), $start, $end, 'spent', $groupedSpent);
            $this->saveGroupedForPrefix(sprintf('no_%s', $model), $start, $end, 'earned', $groupedEarned);
            $this->saveGroupedForPrefix(sprintf('no_%s', $model), $start, $end, 'transferred', $groupedTransferred);

            return $entry;
        }
        Log::debug(sprintf('Found %d statistics in period %s - %s.', count($statistics), $start->format('Y-m-d'), $end->format('Y-m-d')));

        $entry      = ['title' => $title, 'route' => route(sprintf('%s.no-%s', Str::plural($model), $model), [
            $start->format('Y-m-d'),
            $end->format('Y-m-d'),
        ]), 'total_transactions' => 0, 'spent' => [], 'earned' => [], 'transferred' => []];
        $grouped    = [];

        /** @var PeriodStatistic $statistic */
        foreach ($statistics as $statistic) {
            $type                = str_replace(sprintf('no_%s_', $model), '', $statistic->type);
            $id                  = (int) $statistic->transaction_currency_id;
            $currency            = Amount::getTransactionCurrencyById($id);
            $grouped[$type]['count'] ??= 0;
            $grouped[$type][$id] = [
                'amount'                  => (string) $statistic->amount,
                'count'                   => (int) $statistic->count,
                'currency_id'             => $currency->id,
                'currency_name'           => $currency->name,
                'currency_code'           => $currency->code,
                'currency_symbol'         => $currency->symbol,
                'currency_decimal_places' => $currency->decimal_places,
            ];
            $grouped[$type]['count'] += (int) $statistic->count;
        }
        $types      = ['spent', 'earned', 'transferred'];
        foreach ($types as $type) {
            if (array_key_exists($type, $grouped)) {
                $entry['total_transactions'] += $grouped[$type]['count'];
                unset($grouped[$type]['count']);
                $entry[$type] = $grouped[$type];
            }
        }

        return $entry;
    }

    private function groupByCurrency(array $journals): array
    {
        Log::debug('groupByCurrency()');
        $return = ['count' => 0];
        if (0 === count($journals)) {
            return $return;
        }

        /** @var array $journal */
        foreach ($journals as $journal) {
            $currencyId                    = (int) $journal['currency_id'];
            $currencyCode                  = $journal['currency_code'];
            $currencyName                  = $journal['currency_name'];
            $currencySymbol                = $journal['currency_symbol'];
            $currencyDecimalPlaces         = $journal['currency_decimal_places'];
            $foreignCurrencyId             = $journal['foreign_currency_id'];
            $amount                        = (string) ($journal['amount'] ?? '0');

            if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId !== $this->primaryCurrency->id) {
                $amount                = (string) ($journal['pc_amount'] ?? '0');
                $currencyId            = $this->primaryCurrency->id;
                $currencyCode          = $this->primaryCurrency->code;
                $currencyName          = $this->primaryCurrency->name;
                $currencySymbol        = $this->primaryCurrency->symbol;
                $currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
            }
            if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId === $this->primaryCurrency->id) {
                $currencyId            = (int) $foreignCurrencyId;
                $currencyCode          = $journal['foreign_currency_code'];
                $currencyName          = $journal['foreign_currency_name'];
                $currencySymbol        = $journal['foreign_currency_symbol'];
                $currencyDecimalPlaces = $journal['foreign_currency_decimal_places'];
                $amount                = (string) ($journal['foreign_amount'] ?? '0');
            }
            $return[$currencyId] ??= [
                'amount'                  => '0',
                'count'                   => 0,
                'currency_id'             => $currencyId,
                'currency_name'           => $currencyName,
                'currency_code'           => $currencyCode,
                'currency_symbol'         => $currencySymbol,
                'currency_decimal_places' => $currencyDecimalPlaces,
            ];

            $return[$currencyId]['amount'] = bcadd((string) $return[$currencyId]['amount'], $amount);
            ++$return[$currencyId]['count'];
            ++$return['count'];
        }

        return $return;
    }

    private function saveGroupedAsStatistics(Model $model, Carbon $start, Carbon $end, string $type, array $array): void
    {
        unset($array['count']);
        Log::debug(sprintf(
            'saveGroupedAsStatistics(%s #%d, %s, %s, "%s", array(%d))',
            $model::class,
            $model->id,
            $start->format('Y-m-d'),
            $end->format('Y-m-d'),
            $type,
            count($array)
        ));
        foreach ($array as $entry) {
            $this->periodStatisticRepo->saveStatistic($model, $entry['currency_id'], $start, $end, $type, $entry['count'], $entry['amount']);
        }
        if (0 === count($array)) {
            Log::debug('Save empty statistic.');
            $this->periodStatisticRepo->saveStatistic($model, $this->primaryCurrency->id, $start, $end, $type, 0, '0');
        }
    }

    private function saveGroupedForPrefix(string $prefix, Carbon $start, Carbon $end, string $type, array $array): void
    {
        unset($array['count']);
        Log::debug(sprintf(
            'saveGroupedForPrefix("%s", %s, %s, "%s", array(%d))',
            $prefix,
            $start->format('Y-m-d'),
            $end->format('Y-m-d'),
            $type,
            count($array)
        ));
        foreach ($array as $entry) {
            $this->periodStatisticRepo->savePrefixedStatistic($prefix, $entry['currency_id'], $start, $end, $type, $entry['count'], $entry['amount']);
        }
        if (0 === count($array)) {
            Log::debug('Save empty statistic.');
            $this->periodStatisticRepo->savePrefixedStatistic($prefix, $this->primaryCurrency->id, $start, $end, $type, 0, '0');
        }
    }
}
