<commit_before><?php
/**
 * BudgetLimitRepository.php
 * Copyright (c) 2019 <EMAIL>
 *
 * 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\Repositories\Budget;


use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionCurrencyFactory;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Log;

/**
 *
 * Class BudgetLimitRepository
 */
class BudgetLimitRepository implements BudgetLimitRepositoryInterface
{
    private User $user;

    /**
     * Tells you which amount has been budgeted (for the given budgets)
     * in the selected query. Returns a positive amount as a string.
     *
     * @param Carbon              $start
     * @param Carbon              $end
     * @param TransactionCurrency $currency
     * @param Collection|null     $budgets
     *
     * @return string
     */
    public function budgeted(Carbon $start, Carbon $end, TransactionCurrency $currency, ?Collection $budgets = null): string
    {
        $query = BudgetLimit
            ::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
            ->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
            ->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
            ->where('budget_limits.transaction_currency_id', $currency->id)
            ->whereNull('budgets.deleted_at')
            ->where('budgets.user_id', $this->user->id);
        if (null !== $budgets && $budgets->count() > 0) {
            $query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray());
        }

        $set    = $query->get(['budget_limits.*']);
        $result = '0';
        /** @var BudgetLimit $budgetLimit */
        foreach ($set as $budgetLimit) {
            $result = bcadd($budgetLimit->amount, $result);
        }

        return $result;
    }

    /**
     * Destroy a budget limit.
     *
     * @param BudgetLimit $budgetLimit
     */
    public function destroyBudgetLimit(BudgetLimit $budgetLimit): void
    {
        try {
            $budgetLimit->delete();
        } catch (Exception $e) {
            Log::info(sprintf('Could not delete budget limit: %s', $e->getMessage()));
        }
    }

    /**
     * @param Budget              $budget
     * @param TransactionCurrency $currency
     * @param Carbon              $start
     * @param Carbon              $end
     *
     * @return BudgetLimit|null
     */
    public function find(Budget $budget, TransactionCurrency $currency, Carbon $start, Carbon $end): ?BudgetLimit
    {
        return $budget->budgetlimits()
                      ->where('transaction_currency_id', $currency->id)
                      ->where('start_date', $start->format('Y-m-d 00:00:00'))
                      ->where('end_date', $end->format('Y-m-d 23:59:59'))->first();
    }

    /**
     * @param Carbon $start
     * @param Carbon $end
     *
     * @return Collection
     *
     */
    public function getAllBudgetLimits(Carbon $start = null, Carbon $end = null): Collection
    {
        // both are NULL:
        if (null === $start && null === $end) {
            $set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
                              ->with(['budget'])
                              ->where('budgets.user_id', $this->user->id)
                              ->whereNull('budgets.deleted_at')
                              ->get(['budget_limits.*']);

            return $set;
        }
        // one of the two is NULL.
        if (null === $start xor null === $end) {
            $query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
                                ->with(['budget'])
                                ->whereNull('budgets.deleted_at')
                                ->where('budgets.user_id', $this->user->id);
            if (null !== $end) {
                // end date must be before $end.
                $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00'));
            }
            if (null !== $start) {
                // start date must be after $start.
                $query->where('start_date', '>=', $start->format('Y-m-d 00:00:00'));
            }
            $set = $query->get(['budget_limits.*']);

            return $set;
        }
        // neither are NULL:
        $set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
                          ->with(['budget'])
                          ->where('budgets.user_id', $this->user->id)
                          ->whereNull('budgets.deleted_at')
                          ->where(
                              static function (Builder $q5) use ($start, $end) {
                                  $q5->where(
                                      static function (Builder $q1) use ($start, $end) {
                                          $q1->where(
                                              static function (Builder $q2) use ($start, $end) {
                                                  $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00'));
                                                  $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 00:00:00'));
                                              }
                                          )
                                             ->orWhere(
                                                 static function (Builder $q3) use ($start, $end) {
                                                     $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00'));
                                                     $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 00:00:00'));
                                                 }
                                             );
                                      }
                                  )
                                     ->orWhere(
                                         static function (Builder $q4) use ($start, $end) {
                                             // or start is before start AND end is after end.
                                             $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 00:00:00'));
                                             $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00'));
                                         }
                                     );
                              }
                          )->get(['budget_limits.*']);

        return $set;
    }

    /**
     * @param TransactionCurrency $currency<commit_msg>Fix issue submitted over email by Gustav, making sure the "budgeted" amount matches the budgets below.<commit_after><?php
/**
 * BudgetLimitRepository.php
 * Copyright (c) 2019 <EMAIL>
 *
 * 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\Repositories\Budget;


use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionCurrencyFactory;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Log;

/**
 *
 * Class BudgetLimitRepository
 */
class BudgetLimitRepository implements BudgetLimitRepositoryInterface
{
    private User $user;

    /**
     * Tells you which amount has been budgeted (for the given budgets)
     * in the selected query. Returns a positive amount as a string.
     *
     * @param Carbon              $start
     * @param Carbon              $end
     * @param TransactionCurrency $currency
     * @param Collection|null     $budgets
     *
     * @return string
     */
    public function budgeted(Carbon $start, Carbon $end, TransactionCurrency $currency, ?Collection $budgets = null): string
    {
        $query = BudgetLimit
            ::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')

            // same complex where query as below.
            ->where(
                static function (Builder $q5) use ($start, $end) {
                    $q5->where(
                        static function (Builder $q1) use ($start, $end) {
                            $q1->where(
                                static function (Builder $q2) use ($start, $end) {
                                    $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d'));
                                    $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d'));
                                }
                            )
                               ->orWhere(
                                   static function (Builder $q3) use ($start, $end) {
                                       $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d'));
                                       $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
                                   }
                               );
                        }
                    )
                       ->orWhere(
                           static function (Builder $q4) use ($start, $end) {
                               // or start is before start AND end is after end.
                               $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d'));
                               $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
                           }
                       );
                }
            )


            ->where('budget_limits.transaction_currency_id', $currency->id)
            ->whereNull('budgets.deleted_at')
            ->where('budgets.user_id', $this->user->id);
        if (null !== $budgets && $budgets->count() > 0) {
            $query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray());
        }

        $set    = $query->get(['budget_limits.*']);
        $result = '0';
        /** @var BudgetLimit $budgetLimit */
        foreach ($set as $budgetLimit) {
            $result = bcadd($budgetLimit->amount, $result);
        }

        return $result;
    }

    /**
     * Destroy a budget limit.
     *
     * @param BudgetLimit $budgetLimit
     */
    public function destroyBudgetLimit(BudgetLimit $budgetLimit): void
    {
        try {
            $budgetLimit->delete();
        } catch (Exception $e) {
            Log::info(sprintf('Could not delete budget limit: %s', $e->getMessage()));
        }
    }

    /**
     * @param Budget              $budget
     * @param TransactionCurrency $currency
     * @param Carbon              $start
     * @param Carbon              $end
     *
     * @return BudgetLimit|null
     */
    public function find(Budget $budget, TransactionCurrency $currency, Carbon $start, Carbon $end): ?BudgetLimit
    {
        return $budget->budgetlimits()
                      ->where('transaction_currency_id', $currency->id)
                      ->where('start_date', $start->format('Y-m-d 00:00:00'))
                      ->where('end_date', $end->format('Y-m-d 23:59:59'))->first();
    }

    /**
     * @param Carbon $start
     * @param Carbon $end
     *
     * @return Collection
     *
     */
    public function getAllBudgetLimits(Carbon $start = null, Carbon $end = null): Collection
    {
        // both are NULL:
        if (null === $start && null === $end) {
            $set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
                              ->with(['budget'])
                              ->where('budgets.user_id', $this->user->id)
                              ->whereNull('budgets.deleted_at')
                              ->get(['budget_limits.*']);

            return $set;
        }
        // one of the two is NULL.
        if (null === $start xor null === $end) {
            $query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
                                ->with(['budget'])
                                ->whereNull('budgets.deleted_at')
                                ->where('budgets.user_id', $this->user->id);
            if (null !== $end) {
                // end date must be before $end.
                $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00'));
            }
            if (null !== $start) {
                // start date must be after $start.
                $query->where('start_date', '>=', $start->format('Y-m-d 00:00:00'));
            }
            $set = $query->get(['budget_limits.*']);

            return $set;
        }
        // neither are NULL:
        $set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
                          ->with(['budget'])
                          ->where('budgets.user_id', $this->user->id)
                          ->whereNull('budgets.deleted_at')
                          ->where(
                              static function (Builder $q5) use ($start, $end) {
                                  $q5->where(
                                      static function (Builder $q1) use ($start, $end) {
                                          $q1->where(
                                              static function (Builder $q2) use ($start, $end) {
                                                  $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d'));
                                                  $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d'));
                                              }
                                          )
                                             ->orWhere(
                                                 static function (Builder $q3) use ($start, $end) {
                                                     $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d'));
                                                     $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
                                                 }
                                             );
                                      }
                                  )
                                     ->orWhere(
                                         static function (Builder $q4) use ($start, $end) {
                                             // or start is before start AND end is after end.
                                             $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d'));
                                             $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
                                         }
                                     );
                              }
                          )->get(['budget_limits.*']);

        return $set;
    }

    /**
     * @param TransactionCurrency $currency
{'fraction_non_alphanumeric': 0.13295734085318295, 'fraction_numerical': 0.009059818803623927, 'mean_word_length': 1.390362828051054, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 163, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 81, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2267629', 'n_tokens_mistral': 4440, 'n_tokens_neox': 4255, 'n_words': 1000}