<?php

namespace Yajny\Member\Models;

use App\User as Authenticatable;
use DateTime;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Yajny\EventsYj\Events\BonusNew;
use Yajny\EventsYj\Events\CashbackNew;
use Yajny\EventsYj\Events\CashbackUpdated;
use Yajny\EventsYj\Events\ClaimNew;
use Yajny\EventsYj\Events\PayoutNew;
use Yajny\EventsYj\Events\PayoutUpdated;
use Yajny\MailYj\Mails\VerifyEmail;
use Yajny\Member\BalanceTransactions\BonusBalanceTransaction;
use Yajny\Member\Entities\BalanceTransactions\TransactionsBalanceTypeUtil;
use Yajny\Member\Exceptions\CurrencyNotFoundException;
use Yajny\Member\MerchantStatusActivities;
use Yajny\Member\Traits\MemberScopes;
use Yajny\Models\Country;
use Yajny\Models\Coupon;
use Yajny\Models\Store;

class Member extends Authenticatable
{

    use Notifiable;
    use MemberScopes;

    protected $table = 'users';
    protected $primaryKey = 'user_id';
    protected $guard = 'member';

    protected $filterMonth = null;

    protected $filterYear = null;

    protected $guarded = ['id'];

    protected $hidden = [
        'password', 'remember_token',
    ];

    protected $appends = ['photoURL'];

    protected $status_amounts = ['pending' => 0, 'confirmed' => 0, 'declined' => 0];
    protected $redeemed_amounts = ['amount' => 0, 'cashback' => 0, 'reward' => 0];

    private static $declinedBonusCodes = [
        'join_no_refer',
        'refer_bonus',
        'join_with_refer',
        'anniversary_joining_bonus',
        'referral_sale_bonus',
        'loyalty_program'
    ];

    /**
     *
     */
    public static function boot()
    {
        parent::boot();
        static::creating(function ($member) {
            if (empty($member->referrer_code) || $member->referrer_code == 'undef') $member->referrer_code = null;
        });
        // TODO: update for new attr for merchant
        static::updated(function ($member) {
            $actionby_id = null;
            $changes = $member->getChanges();
            if (isset($changes['is_merchant']) && $changes['is_merchant'] == 1) {
                if (isset($changes['type'])) {
                    if ($changes['type'] == 'affiliate') {
                        self::declineMerchantBonuses([$member->user_id]);
                    }
                } else {
                    if (isset($changes['valid_merchant']) && $changes['valid_merchant'] == 1) {
                        self::ChangeMerchantStatus($member->user_id, 'validate', $actionby_id);
                    } else {
                        self::ChangeMerchantStatus($member->user_id, 'tagged', $actionby_id);
                    }
                }
            }

//            if (isset($changes['phone_number_verified']) && $changes['phone_number_verified'] && !empty($member->referrer_code)) {
//                $referrer = Member::where('referral_code', $member->referrer_code)->first();
//                if ($referrer) {
//                    // get the referrer id by ref code
//                    self::addingReferralBonuses($referrer, $member->user_id);
//                    $member->referrer_id = $referrer->user_id;
//                    $member->save();
//                }
//            }
//            self::checkingReferralBonuses($member);
        });
    }

    /**
     * @param $merchant_id
     * @param $actionby
     * @return bool
     */
    public static function ChangeMerchantStatus($merchant_id, $status, $actionby)
    {
        $record = new \Yajny\Member\Models\MerchantStatusActivities();

        $record->user_id = $merchant_id;
        $record->action = $status;
        $record->action_by = $actionby;
        return $record->save();
    }

    public static function addingReferralBonuses($member, $referral_id)
    {
        $member->total_referred = $member->total_referred + 1;
        $member->save();

        $bonus = NewBonusType::where('code', 'refer_user_purchase')->first();
        if ($bonus) {
            $userBonus = new UserNewBonus();
            $userBonus->bonus_type_id = $bonus->id;
            $userBonus->user_id = $member->user_id;
            $userBonus->status = 'pending';
            $userBonus->is_referral = 1;
            $userBonus->referral_id = $referral_id;
            $userBonus->amount = $bonus->amount;
            $userBonus->save();
            $balanceTransaction = new BonusBalanceTransaction($member->user_id, $userBonus->amount, $userBonus->id, $userBonus->status, $userBonus->created_at);
            $balanceTransaction->saveTransaction();
            $userBonus = new UserNewBonus();
            $userBonus->bonus_type_id = $bonus->id;
            $userBonus->user_id = $referral_id;
            $userBonus->status = 'pending';
            $userBonus->is_referral = 1;
            $userBonus->amount = $bonus->amount;
            $userBonus->save();
            $balanceTransaction = new BonusBalanceTransaction($member->user_id, $userBonus->amount, $userBonus->id, $userBonus->status, $userBonus->created_at);
            $balanceTransaction->saveTransaction();
        }
    }

    public static function checkingReferralBonuses($member)
    {
        $userBonus = new UserNewBonus();
        $bonus = false;
        $total_referred = self::where([['referral_id', $member->user_id], ['created_at', '>', '2022-11-15'], ['phone_number_verified', true]])->count();
        if ($total_referred == 5) {
            $bonus = NewBonusType::where('code', 'refer_5_user')->first();
        } elseif ($total_referred == 10) {
            $bonus = NewBonusType::where('code', 'refer_10_user')->first();
        } elseif ($total_referred == 20) {
            $bonus = NewBonusType::where('code', 'refer_20_user')->first();
        }
        if ($bonus) {
            if (UserNewBonus::where([['user_id', $member->user_id], ['bonus_type_id', $bonus->id]])->count())
                return;
            $userBonus->bonus_type_id = $bonus->id;
            $userBonus->user_id = $member->user_id;
            $userBonus->status = 'confirmed';
            $userBonus->is_referral = 1;
            $userBonus->amount = $bonus->amount;
            $userBonus->save();
            $balanceTransaction = new BonusBalanceTransaction($member->user_id, $userBonus->amount, $userBonus->id, $userBonus->status, $userBonus->created_at);
            $balanceTransaction->saveTransaction();

        }
    }

    /**
     * @return mixed
     */
    public function subscription()
    {
        return $this->hasOne('Yajny\Member\Models\Subscription', 'user_id', 'user_id');
    }


    /**
     * @return mixed
     */
    public function merchantActivities()
    {
        return $this->hasMany('Yajny\Member\Models\MerchantStatusActivities', 'user_id', 'user_id')->orderBy('id', 'DESC');
    }

    /**
     * @return mixed
     */
    public function taggedMerchantSince($user_id)
    {
        return \Yajny\Member\Models\MerchantStatusActivities::where('user_id', $user_id)
            ->orderBy('id', 'DESC')
            ->where('action', 'tagged')->first();
    }

    /**
     * @return mixed
     */
    public function validatedMerchantSince($user_id)
    {
        return \Yajny\Member\Models\MerchantStatusActivities::where('user_id', $user_id)
            ->orderBy('id', 'DESC')
            ->where('action', 'validate')->first();
    }

    /**
     * @return mixed
     */
    public function revokedMerchantSince($user_id)
    {
        return \Yajny\Member\Models\MerchantStatusActivities::where('user_id', $user_id)
            ->orderBy('id', 'DESC')
            ->where('action', 'revoke')->first();
    }

    /**
     * @return mixed
     */
    public function clicks()
    {
        return $this->hasMany('Yajny\Member\Models\Click', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function moderators()
    {
        return $this->hasMany('Yajny\Member\Models\Member', 'parent_id', 'user_id')->where('is_merchant', 1);
    }

    /**
     * @return mixed
     */
    public function bonuses()
    {
        return $this->hasMany('Yajny\Member\Models\UserBonus', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function newBonuses()
    {
        return $this->hasMany('Yajny\Member\Models\UserNewBonus', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function referrals()
    {
        return $this->hasMany('Yajny\Member\Models\UserBonus', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function referral_invites()
    {
        return $this->hasMany('Yajny\Member\Models\ReferralInvites', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function referred_users()
    {
        return $this->hasMany('Yajny\Member\Models\Member', 'referrer_code', 'referral_code');
    }

    /**
     * @return mixed
     */
    public function sales()
    {
        return $this->hasMany('Yajny\Member\Models\UserSales', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function referral_sales()
    {
        return $this->hasMany('Yajny\Member\Models\UserReferralSales', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function claims()
    {
        return $this->hasMany('Yajny\Member\Models\UserClaim', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function payout_methods()
    {
        return $this->hasMany('Yajny\Member\Models\UserPayoutMethods', 'user_id', 'user_id')
            ->whereHas('info');
    }

    /**
     * @return mixed
     */
    public function payout_methods_without_global_scopes()
    {
        return $this->hasMany('Yajny\Member\Models\UserPayoutMethods', 'user_id', 'user_id')
            ->whereHas('info', function ($q) {
                $q->withoutGlobalScopes();
            });
    }

    /**
     * @return mixed
     */
    public function payouts()
    {
        return $this->hasMany('Yajny\Member\Models\UserPayouts', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function links()
    {
        return $this->hasMany('Yajny\Member\Models\UserLink', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function logins()
    {
        return $this->hasMany('Yajny\Member\Models\UserLogin', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function favourites()
    {
        return $this->hasMany('Yajny\Member\Models\UserFavourite', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function balanceTransactions()
    {
        return $this->hasMany('Yajny\Member\Models\BalanceTransaction', 'user_id', 'user_id')->orderBy('created_at', 'desc')->orderBy('id', 'desc');
    }

    /**
     * @return mixed
     */
    public function userExpiredAvailableAmount()
    {
        return $this->hasMany('Yajny\Member\Models\UserExpiredAvailableAmount', 'user_id', 'user_id')->orderBy('created_at', 'desc')->orderBy('id', 'desc');
    }

    /**
     * @return mixed
     */
    public function balance(): HasOne
    {
        return $this->hasOne('Yajny\Member\Models\UsersBalance', 'user_id', 'user_id');
    }

    /**
     * @return mixed
     */
    public function notifications()
    {
        return $this->hasMany('Yajny\Member\Models\UserNotification', 'user_id', 'user_id')->orderBy('created_at', 'desc')->orderBy('id', 'desc');
    }

    /**
     * @return HasOne
     */
    public function registrationBonus(): HasOne
    {
        return $this->hasOne(RegistrationCode::class, 'code', 'register_code');
    }

    /**
     * @param int $strength
     * @return string
     * @throws \Exception
     */
    public function generate_string($strength = 10)
    {
        $permitted_chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

        $input_length = strlen($permitted_chars);
        $random_string = '';
        for ($i = 0; $i < $strength; $i++) {
            $random_character = $permitted_chars[random_int(0, $input_length - 1)];
            $random_string .= $random_character;
        }

        return $random_string;
    }

    /**
     * @param $offer_title
     * @param $offer_link
     * @param $store_id
     * @return mixed
     * @throws \Exception
     */
    public function createLink($offer_title, $offer_link, $store_id)
    {
        $offer_code = $this->generate_string(10);
        return $this->links()->save(new UserLink(['offer_title' => $offer_title, 'offer_link' => $offer_link, 'store_id' => $store_id, 'code' => $offer_code]));
    }

    /**
     * @param $balanceType
     * @param $amountToAdd
     * @param false $allowNegative
     */
    public function incrementUserBalance($balanceType, $amountToAdd, $allowNegative = false)
    {
        if ($balanceType == TransactionsBalanceTypeUtil::PENDING) {
//            if (!$allowNegative && round($this->pending_balance + $amountToAdd, 2) < 0)
//                throw new NegativeUserBalanceException();

            DB::statement("UPDATE users SET pending_balance = pending_balance + {$amountToAdd} WHERE user_id = {$this->user_id}");
            $this->pending_balance = $this->pending_balance + $amountToAdd;
        } else if ($balanceType == TransactionsBalanceTypeUtil::CONFIRMED) {
//            if (!$allowNegative && round($this->confirmed_balance + $amountToAdd, 2) < 0)
//                throw new NegativeUserBalanceException();

            DB::statement("UPDATE users SET confirmed_balance = confirmed_balance + {$amountToAdd} WHERE user_id = {$this->user_id}");
            $this->confirmed_balance = $this->confirmed_balance + $amountToAdd;
        }
    }

    /**
     * @param $monthYear
     */
    public function filterDate($monthYear)
    {
        if (strlen($monthYear) == 6) {
            $this->month = substr($monthYear, 4, 2);
            $this->year = substr($monthYear, 0, 4);
        } else {
            $this->month = date('m');
            $this->year = date('Y');
        }
    }


    /**
     * @param $object
     */
    public function filterSourceType($object)
    {
        if ($object != '') {
            $this->source_type = $object;
        }
    }


    /**
     * @return array
     */
    public function getCashbackMonths()
    {
        return self::getMonthsByData('Yajny\Member\Models\UserSales', $this->user_id);
    }

    /**
     * @return array
     */
    public function getReferralMonths()
    {
        return self::getMonthsByData('Yajny\Member\Models\UserReferralSales', $this->user_id);
    }

    /**
     * @return array
     */
    public function getBonusMonths()
    {
        return self::getMonthsByData('Yajny\Member\Models\UserBonus', $this->user_id);
    }

    /**
     * @return array
     */
    public function getPayoutMonths()
    {
        return self::getMonthsByData('Yajny\Member\Models\UserPayouts', $this->user_id);
    }

    /**
     * @return array
     */
    public function getClickMonths()
    {
        return self::getMonthsByData('Yajny\Member\Models\Click', $this->user_id);
    }

    /**
     * @param $limit
     * @return mixed
     */
    public function getRecentClicks($limit)
    {
        return $this->clicks()->with(['store' => function ($q) {
            $q->selectRaw(Store::colSelect());
        }])->OrderByClick()->limit($limit)->get();
    }

    /**
     * @param $monthYear
     * @return mixed
     */
    public function filteredClicks($monthYear)
    {
        $this->filterDate($monthYear);

        return $this->clicks()->ByMonth($this->month, $this->year)->with(['store' => function ($q) {
            $q->selectRaw(Store::colSelect());
        }])->OrderByClick()->get();
    }

    /**
     * @param $object
     * @param int $limit
     * @param int $page
     * @return mixed
     */
    public function getHistoryClicks($object, $limit = 20, $page = 1)
    {
        $this->filterSourceType($object);

        return $this->clicks()->with(['store' => function ($q) {
            $q->with('store_coupons')->selectRaw(Store::colSelect());

        }])->OrderByClickTime()->paginate($limit, ['*'], 'page', $page);
    }

    /**
     * @param $monthYear
     * @param $status
     * @return mixed
     */
    public function filteredBonuses($monthYear, $status)
    {
        $this->filterDate($monthYear);
        return $this->bonuses()
            ->with([
                'bonus_type' => function ($q) {
                    $q->selectRaw(BonusType::colSelect());
                },
                'loyaltyProgram' => function ($q) {
                    $q->selectRaw(LoyaltyProgram::colSelect());
                }
            ])
            ->ByMonth($this->month, $this->year)->ByStatus($status)->OrderByBonus()->get();
    }


    /**
     * @param $monthYear
     * @param $status
     * @return mixed
     */
    public function filteredSales($monthYear, $status)
    {

        $this->filterDate($monthYear);


        return $this->sales()->ByMonth($this->month, $this->year)->ByStatus($status)->with(['store' => function ($q) {
            $q->selectRaw(Store::colSelect());
        }])->OrderBySale()->get();
    }

    /**
     * @param $monthYear
     * @param $status
     * @return mixed
     */
    public function filteredReferralSales($monthYear, $status)
    {
        $this->filterDate($monthYear);

        return $this->referral_sales()->ByMonth($this->month, $this->year)->ByStatus($status)->with(['store' => function ($q) {
            $q->selectRaw(Store::colSelect());
        }])->with('shopper')->OrderBySale()->get();
    }

    /**
     * @param $monthYear
     * @return mixed
     */
    public function getPayouts($monthYear)
    {
        $this->filterDate($monthYear);

        return $this->payouts()
            ->with(['method_info' => function ($q) {
                $q->selectRaw(PayoutMethods::colSelect());
            }])
            ->ByMonth($this->month, $this->year)->orderBy('id', 'DESC')->get();
    }

    /**
     * @param $sale
     * @param false $is_silent
     */
    public function assignCashback($sale, $is_silent = false)
    {
        $user = $this;
        $userSale = new UserSales;
        foreach ($userSale->copyColumns as $info)
            $userSale->{$info} = $sale->{$info};

        $userSale->sales_id = $sale->id;
        $userSale->cashback = round(($sale->comission * $sale->cashback_percent) / 100, 2);
        $userSale->local_cashback = round(($sale->local_comission * $sale->cashback_percent) / 100, 2);
        $userSale->expiration_date = Carbon::now()->addDays(365);

        if ($sale->currency_conversions) {
            foreach ($sale->currency_conversions as $isoCode => $rate) {
                if (in_array($isoCode, ['EGP', 'SAR', 'AED', 'KWD']))
                    $userSale['cashback_' . strtolower($isoCode)] =
                        $sale->currency == $isoCode
                            ? $userSale->local_cashback
                            : $userSale->cashback * $rate;
            }
        } else {
            $currenciesConversionRate = $this->currenciesConversionsRate($sale->transaction_time);
            $cashbackInDollar = $userSale->local_cashback / $currenciesConversionRate[$sale->currency];
            foreach (['EGP', 'SAR', 'AED', 'KWD'] as $isoCode) {
                $userSale['cashback_' . strtolower($isoCode)] =
                    $sale->currency == $isoCode
                        ? $userSale->local_cashback
                        : $cashbackInDollar * $currenciesConversionRate[$isoCode];
            }
        }
        $userSale = $user->sales()->save($userSale);

        // $user->notifyCashbackTracked($userSale);
        event(new CashbackNew($this, $userSale, $is_silent));
    }

    /**
     * @param $changes
     * @param $userSale
     * @param $sale
     * @param false $is_silent
     */
    public function updateCashback($changes, $userSale, $sale, $is_silent = false)
    {
        if (isset($changes['comission']) ||
            isset($changes['cashback_percent']) ||
            isset($changes['order_amount'])
        ) {
            $userSale->cashback = round(($sale->comission * $sale->cashback_percent) / 100, 2);
            $userSale->local_cashback = round(($sale->local_comission * $sale->cashback_percent) / 100, 2);
            $userSale->order_amount = $sale->order_amount;
            $userSale->local_order_amount = $sale->local_order_amount;

            if ($sale->currency_conversions) {
                foreach ($sale->currency_conversions as $isoCode => $rate) {
                    if (in_array($isoCode, ['EGP', 'SAR', 'AED', 'KWD']))
                        $userSale['cashback_' . strtolower($isoCode)] =
                            $sale->currency == $isoCode
                            ? $userSale->local_cashback
                            : $userSale->cashback * $rate;
                }
            } else {
                $currenciesConversionRate = $this->currenciesConversionsRate($userSale->transaction_time);
                $cashbackInDollar = $userSale->local_cashback / $currenciesConversionRate[$userSale->currency];
                foreach (['EGP', 'SAR', 'AED', 'KWD'] as $isoCode) {
                    $userSale['cashback_' . strtolower($isoCode)] =
                        $userSale->currency == $isoCode
                        ? $userSale->local_cashback
                        : $cashbackInDollar * $currenciesConversionRate[$isoCode];
                }
            }
        }

        if (isset($changes['status'])) {
            $userSale->status = $sale->status;
            if ($userSale->status == 'confirmed') $userSale->expiration_date = Carbon::now()->addDays(365);
        }
        $userSale->save();
        if (isset($changes['comission']) || isset($changes['status'])) {
            event(new CashbackUpdated($this, $userSale, $is_silent));
        }
    }

    /**
     * @param $claim_id
     * @return mixed
     */
    public function getClaimInfo($claim_id)
    {
        return $this->claims()->where('id', $claim_id)->with('comments')->with(['store' => function ($q) {
            $q->selectRaw(Store::colSelect());
        }])->first();
    }

    /**
     * @return mixed
     */
    public function getClaims()
    {
        return $this->claims()->with(['store' => function ($q) {
            $q->selectRaw(Store::colSelect());
        }])->get();
    }

    /**
     * @return mixed
     */
    public function getClaimableStores()
    {
        return $this->clicks()->claimableClicks()->select('store_id')
            ->with(['store' => function ($q) {
                $q->selectRaw(Store::colSelect());
            }])
            ->groupBy('store_id')
            ->get();
    }

    /**
     * @param $store_id
     * @return mixed
     */
    public function getClaimableClicks($store_id)
    {
        return $this->clicks()->claimableClicks()->where('store_id', $store_id)->get();
    }

    /**
     * @param $claimInfo
     * @return UserClaimComment
     */
    public function makeComment($claimInfo)
    {
        $comment = new UserClaimComment(['claim_id' => $claimInfo['claim_id'], 'comment' => $claimInfo['comment'], 'commented_by' => 'user']);
        $comment->save();
        return $comment;
    }

    /**
     * @param $claimInfo
     * @return UserClaim
     */
    public function makeClaim($claimInfo)
    {
        $stripColumns = ['lang', 'user_token', 'prefix', 'from', 'web_token', 'token', 'appy', 'region', 'localization', 'userCurrency', 'attachments'];
        foreach ($stripColumns as $column)
            unset($claimInfo[$column]);

        $claim = new UserClaim(array_merge($claimInfo, ['user_id' => $this->user_id]));
        $claim->updateClickInfo();
        $claim->save();

        event(new ClaimNew($this, $claim));
        return $claim;
    }

    /**
     * @return mixed
     */
    public function comments()
    {
        return $this->hasMany('Yajny\Member\Models\UserClaimComment', 'claim_id', 'id');
    }

    /**
     * @return array
     */
    public function getBalances()
    {
        $earning = ['cashback' => $this->getTotalCashback(), 'reward' => $this->getTotalRewards(), 'paid' => $this->getPayoutAmount()];
        $earning['balance'] = $earning['cashback']['confirmed'] + $earning['reward']['confirmed'] - $earning['paid']['amount'];
        return $earning;
    }

    /**
     * @return int[]
     */
    public function getTotalCashback()
    {
        $cashback = $this->status_amounts;
        $amts = $this->sales()->groupBy('status')->selectRaw('status,SUM(cashback) amount')->get()->toArray();
        foreach ($amts as $amt)
            $cashback[$amt['status']] = $amt['amount'];

        return $cashback;
    }

    /**
     * @return int[]
     */
    public function getTotalRewards()
    {
        $rewards = $this->status_amounts;
        $amts = $this->bonuses()->groupBy('status')->selectRaw('status,SUM(amount) amount')->get()->toArray();
        foreach ($amts as $amt)
            $rewards[$amt['status']] += $amt['amount'];
        $amts = $this->newBonuses()->groupBy('status')->selectRaw('status,SUM(amount) amount')->get()->toArray();
        foreach ($amts as $amt)
            $rewards[$amt['status']] += $amt['amount'];
        return $rewards;
    }

    /**
     * @return int[]
     */
    public function getTotalReferrals()
    {
        $referal = $this->status_amounts;

        $amts = $this->referral_sales()->groupBy('status')->selectRaw('status,SUM(referral_amount) amount')->get()->toArray();
        foreach ($amts as $amt)
            $referal[$amt['status']] += $amt['amount'];

        return $referal;
    }

    /**
     * @return int[]
     */
    public function getPayoutAmount()
    {
        $redeemed_amounts = $this->redeemed_amounts;
        $amts = $this->payouts()->whereNotIn('status', ['declined', 'failed'])->selectRaw('SUM(amount) amount, SUM(cashback_amount) cashback, SUM(reward_amount) reward')->first()->toArray();
        $redeemed_amounts['cashback'] = $amts['cashback'] + 0;
        $redeemed_amounts['reward'] = $amts['reward'] + 0;
        $redeemed_amounts['amount'] = $amts['amount'] + 0;
        return $redeemed_amounts;
    }

    /**
     * @param $amount
     * @param $method_code
     * @param $account
     * @return array|string[]
     */
    public function raisePayout($amount, $method_code, $account, $max_ammount = null)
    {
        $payoutRule=DB::table('options_manager')->where('option_key',"payout_rule")->first();
        $usedRule="oldRule";
        if ($payoutRule->option_value==1)
            $usedRule="newRule";
        //get user country because the mobile is currently sending USD only
        $countryCode = $this->country_code;
        $userCurrencyCode = 'USD';
        if ($countryCode && $countryCode != "global") {
            $country = Country::where('code', $countryCode)->first();
            $userCurrencyCode = $country->default_currency;
        }

        $method = PayoutMethods::where('code', $method_code)->where('enabled', true)->first();
        if (!$method) {
            // return error -> Method not found
            return ['payment_method' => "method_error"];
        }

        $userPayMode = $this->payout_methods()->where('method_code', $method_code)->where('account', $account)->first();

        if (!$userPayMode) {
            // return error -> Method not found
            return ['payment_mode' => "mode_error"];
        }
        if ($amount < $method->minimum_amount) {
            // return error -> Requesting less than Minuimum amount
            return ['payment_declined' => __('validation.less_than_min_amount')];
        }
        // check for maximum allowed amount to withdraw
        //if ($amount>$max_ammount)
        //    return ['payment_declined' => __('validation.more_than_max_amount')];

        $amountInUserCurrency = $amount;
        $userBalance = UsersBalance::where('user_id', $this->user_id)->first();
        // method currency is USD
        if ($method->default_currency == "USD") {
            // check for cashback more than bonus
            if ($usedRule=="newRule") {
            if ($userBalance->confirmed_cashback < $userBalance->confirmed_bonus) {
                if ($amount > 2 * $userBalance->confirmed_cashback)
                    return ['payment_declined' => "cashback amount less than min amount"];
            }
            }
            if ($userBalance->available_amount < $amount) {
                return ['payment_declined' => __('validation.more_than_max_amount')];
            }
        } // payout method used is different from USD
        else {
            // check for cashback more than bonus
            if ($usedRule=="newRule") {
                if ($userBalance['confirmed_cashback_' . strtolower($method->default_currency)] < $userBalance['confirmed_bonus_' . strtolower($method->default_currency)]) {
                    if ($amount > 2 * $userBalance['confirmed_cashback_' . strtolower($method->default_currency)])
                        return ['payment_declined' => "cashback amount less than min amount"];
                }
            }
            if ($userBalance['available_amount_' . strtolower($method->default_currency)] < $amountInUserCurrency)
                return ['payment_declined' => __('validation.more_than_max_amount')];
        }
//        if ($method->default_currency != $userCurrencyCode) {
//            $userCurrency = Currencies::where('iso_code', $userCurrencyCode)->first();
//            $methodCurrency = Currencies::where('iso_code', strtoupper($method->default_currency))->first();
//            $amountInUserCurrency = $amount * $userCurrency->conversion_rate / $methodCurrency->conversion_rate;
//            dd($amountInUserCurrency,$methodCurrency,$userCurrency);
//        }
//        if ($userCurrency == 'USD') {
//            if ($userBalance->available_amount < $amountInUserCurrency)
//                return ['payment_declined' => __('validation.more_than_max_amount')];
//        } else {
//            if ($userBalance['available_amount_' . strtolower($userCurrency)] < $amountInUserCurrency)
//                return ['payment_declined' => __('validation.more_than_max_amount')];
//        }

        $fees = null;
        $methodFee = $method->fee;
        $conversionRate=Currencies::where('iso_code', $method->default_currency)->first()->conversion_rate;
        if ($methodFee) {
            if ($this->is_merchant) {
                $methodFee->amount = $methodFee->merchant_amount;
            }
            if ($methodFee->type == 'percentage')
                if ($method->default_currency!="USD"){
                    $fees= ($amount/$conversionRate)* $methodFee->amount / 100;
                    if ($methodFee->min_amount)
                        $fees = max($fees, $methodFee->min_amount);
                }else{
                    $fees= $amount * $methodFee->amount / 100;
                    if ($methodFee->min_amount)
                        $fees = max($fees, $methodFee->min_amount);
                }

        }
       // $userCurrency = Currencies::where('iso_code', $userCurrencyCode)->first();
        $amountInUSD = $amountInUserCurrency;
        $payout = new UserPayouts;
        $payout->method_code = $method_code;

        $payout->amount = $amount/$conversionRate;
        $payout->local_final_amount = $amount;
        $payout->final_amount =$amount/$conversionRate;

        $payout->cashback_amount = 0;
        $payout->reward_amount = 0;
        $payout->account = $account;
        $payout->currency_rate = $conversionRate;
        $payout->local_currency = $method->default_currency;
        $payout->local_amount = $amount;
        $payout->status = 'created';
        if ($fees) {
            $feesInUSD = $fees;
            $payout->fees_amount = $feesInUSD;
            $payout->local_fees_amount = $feesInUSD;
            $payout->final_amount -= $feesInUSD;
            $payout->local_final_amount -= $payout->local_fees_amount;
            $payout->fees_data = json_encode([
                'amount' => $methodFee->amount,
                'type' => $methodFee->type,
                'min_amount' => $methodFee->min_amount
            ]);
        }

        $this->payouts()->save($payout);
        event(new  PayoutNew($this, $payout));
        return [
            "payout_id" => $payout->payout_id,
            "method_info" => $payout->method_info->name,
            "account" => $payout->account,
            "amount" => \App\Helpers\FormateHelper::currencyFormat($payout->local_amount, Currencies::where('iso_code', $method->default_currency)->first()),
            "status" => $payout->status,
            "note" => $payout->note
        ];
    }

    /**
     * @param $payout
     * @return \Exception|\PayPal\Exception\PayPalConnectionException|Exception
     */
    function processPayout($payout)
    {
        $paypalPayout = new PaypalPayout;
        $paypal_payout = $paypalPayout->payPal_payOut($payout->account, $payout->amount);
        $retType = class_basename(get_class($paypal_payout));

        // success
        if ($retType == 'PayoutBatch') {
            $payout->status = 'processing';
            $payout->payment_ref_num = $paypal_payout->batch_header->payout_batch_id ?? null;
            $payout->api_status = $paypal_payout->batch_header->batch_status;
            $payout->api_response = $paypal_payout->toJson();

        } else if ($retType == 'PayPalConnectionException') {
            // paypal failed
            $payout->status = 'declined';
            $payout->payment_ref_num = null;
            $payout->api_status = 'failed';
            $payout->api_response = json_encode($paypal_payout->getData());

        } else if ($retType == 'Exception') {
            $payout->status = 'declined';
            // generic Exception
            $payout->payment_ref_num = null;
            $payout->api_status = 'failed';
            $payout->api_response = json_encode(['code' => $paypal_payout->getCode(), 'message' => $paypal_payout->getMessage()]);

        }

        $payout->save();
        return $paypal_payout;
    }

    /**
     * @param $payout
     * @param $status_mapping
     * @return bool
     */
    function updatePayoutStatus($payout, $status_mapping)
    {
        $paypalPayout = new PaypalPayout;
        $paypal_payout = $paypalPayout->getBatchStatus($payout->payment_ref_num);
        $success = false;
        $retType = class_basename(get_class($paypal_payout));

        // sucess
        if ($retType == 'PayoutBatch') {
            $success = true;
            $payout->api_status = $paypal_payout->batch_header->batch_status;
            $payout->status = $status_mapping[$paypal_payout->batch_header->batch_status];
            $payout->api_response = $paypal_payout->toJson();
        } else if ($retType == 'PayPalConnectionException') {
            // paypal failed
            $payout->status = 'declined';
            $payout->payment_ref_num = null;
            $payout->api_status = 'failed';
            $payout->api_response = json_encode($paypal_payout->getData());

            $payout->api_status = 'failed';
            $payout->status = 'declined';
            $payout->api_response = $paypal_payout->toJson();
        } else if ($retType == 'Exception') {
            $payout->status = 'declined';
            // generic Exception
            $payout->api_status = 'failed';
            $payout->status = 'declined';
            $payout->api_response = json_encode(['code' => $paypal_payout->getCode(), 'message' => $paypal_payout->getMessage()]);
        }

        $payout->save();

        event(new PayoutUpdated($this, $payout));

        return $success;
    }

    /**
     * @return mixed
     */
    public function verificationUrl()
    {
        return url()->temporarySignedRoute(
            'verification.verify',
            Carbon::now()->addMinutes(config('auth.verification.expire', 60)),
            ['id' => $this->user_id, 'noBtnApp' => true]);
    }

    /**
     *
     */
    public function sendEmailVerificationNotification()
    {
        Mail::to($this->email)->locale($this->user_lang)->queue(new VerifyEmail($this));
    }

    /**
     *
     */
    public function rewardBonuses()
    {
        $bonuses = BonusType::all()->mapWithKeys(function ($bonusType) {
            return [$bonusType->code => $bonusType->toArray()];
        });

        $join_bonus = false; // assume there is no bonus for user
        $newAmount = null;
        $newQualifyingAmount = null;
        if ($this->registrationBonus) {
            $newAmount = $this->registrationBonus->bonus_amount;
            $newQualifyingAmount = $this->registrationBonus->bonus_qualifying_amount;
            $this->registrationBonus()->update([
                'usage_count' => DB::raw('usage_count + 1')
            ]);
        }

        $referrer = Member::where('referral_code', $this->referrer_code)->first();
        if ($referrer && isset($bonuses['refer_bonus'])) {
            // get the referrer id by ref code
            $this->referrer_id = $referrer->user_id;
            $this->save();
        }
//            if (!$referrer->is_merchant) {
        // joining bonus when referred
//                $join_bonus = 'join_with_refer';

        // assign bonus to referrer
//                $refAward = $referrer->bonuses()->save(new UserBonus([
//                    'bonus_code' => 'refer_bonus',
//                    'referred_user_id' => $this->user_id,
//                    'amount' => $newAmount ?? $bonuses['refer_bonus']['amount'],
//                    'qualifying_amount' => $newQualifyingAmount ?? $bonuses['refer_bonus']['qualifying_amount'],
//                    'awarded_on' => date('Y-m-d'),
//                    'expires_on' => date('Y-m-d', strtotime('now + ' . $bonuses['refer_bonus']['validity_days'] . ' days')),
//                    'status' => 'pending'
//                ]));
//
//                // send email
//                event(new BonusNew($referrer, $refAward));
//            } else {
//                $join_bonus = false; // assume there is no bonus for user
//            }
//        } else if (isset($bonuses['join_no_refer'])) {
//            // if joining bonus is set without referrer
////            $join_bonus = 'join_no_refer';
//            $join_bonus = false;
//        }

        // if either of the bonus is enabled and user is not merchant
//        if ($join_bonus && !$this->is_merchant) {
//            $joinAward = $this->bonuses()->save(new UserBonus([
//                'bonus_code' => $join_bonus,
//                'amount' => $newAmount ?? $bonuses[$join_bonus]['amount'],
//                'qualifying_amount' => $newQualifyingAmount ?? $bonuses[$join_bonus]['qualifying_amount'],
//                'awarded_on' => date('Y-m-d'),
//                'expires_on' => date('Y-m-d', strtotime('now + ' . $bonuses[$join_bonus]['validity_days'] . ' days')),
//                'status' => 'pending'
//            ]));
//
//            // send email
//            event(new BonusNew($this, $joinAward));
//        }
    }

    /**
     *
     */
    public function applyReferralScheme()
    {
        // if referral scheme is enabled, set the Current  validity and percent
        if (config('settingConfig.referral_enabled') == 'Y') {
            $this->referral_percent = config('settingConfig.referral_percent');
            $this->referral_validity = date('Y-m-d', strtotime("now + " . config('settingConfig.referral_validity') . " days"));
            $this->save();
        }
    }

    /**
     * @return string
     */
    public function getPhotoURLAttribute()
    {
        $defaultPhoto = 'https://yajny.nyc3.digitaloceanspaces.com/public/icons/old-user.png';
        if (filter_var($this->photo, FILTER_VALIDATE_URL)) {
            if (!@file_get_contents($this->photo))
                return $defaultPhoto;
            else
                return $this->photo;
        }
        if ($this->photo)
            return Storage::disk()->url($this->photo);
        return $defaultPhoto;
    }

    /**
     * @return mixed
     */
    public static function getAllTimeUserLeaderboard()
    {
        $selectFields = 'SELECT user_id, SUM(cashback) as totalCashback FROM user_sales';
        $where = 'WHERE status != "declined" AND is_sandbox = 0';
        $groupBy = 'GROUP BY user_id ORDER BY totalCashback DESC LIMIT 10';
        $allTimeUsers = DB::select("{$selectFields} {$where} {$groupBy}");
        return self::mapLeaderboardData($allTimeUsers);
    }

    /**
     * @return mixed
     */
    public static function getThisMonthUserLeaderboard()
    {
        $currentDate = Carbon::now();
        $selectFields = 'SELECT user_id, SUM(cashback) as totalCashback FROM user_sales';
        $where = 'WHERE status != "declined" AND is_sandbox = 0';
        $where .= " AND transaction_time BETWEEN '{$currentDate->startOfMonth()->toDateTimeString()}' AND '{$currentDate->endOfMonth()->toDateTimeString()}'";
        $groupBy = 'GROUP BY user_id ORDER BY totalCashback DESC LIMIT 10';
        $thisMonthUsers = DB::select("{$selectFields} {$where} {$groupBy}");
        return self::mapLeaderboardData($thisMonthUsers);
    }

    /**
     * @param $leaderboardData
     * @return mixed
     */
    private static function mapLeaderboardData($leaderboardData)
    {
        //Get user IDs in the results
        $userIDs = [];
        foreach ($leaderboardData as $user) {
            $userIDs[$user->user_id] = true;
        }
        $userIDs = array_keys($userIDs);

        //Find users by IDs
        $users = Member::find($userIDs)->mapWithKeys(function ($user) {
            return [$user->user_id => $user];
        });

        //Map user IDs to users and remove the records with no users
        foreach ($leaderboardData as $index => $user) {
            if (isset($users[$user->user_id]))
                $user->user = $users[$user->user_id];
            else
                unset($leaderboardData[$index]);
        }
        return $leaderboardData;
    }

    /**
     * @param $countryCode
     */
    public function setCountryCodeAttribute($countryCode)
    {
        $this->attributes['country_code'] = $countryCode;
        $country = Country::where('code', $countryCode)->first();
        $this->attributes['currency_code'] = $country->default_currency ?? 'USD';
    }

    /**
     * @return mixed
     */
    public function getUserCurrency()
    {
        $allCurrencies = Cache::rememberForever('currencies', function () {
            return Currencies::all()->mapWithKeys(function ($currency) {
                return [$currency->iso_code => $currency];
            });
        });
        return $allCurrencies[$this->currency_code];
    }

    /**
     * @param int $limit
     * @return array
     */
    public function getTransactionsPaginationSettings($limit = 20)
    {
        $totalCount = UserSales::where('user_id', $this->user_id)->count() + UserBonus::where('user_id', $this->user_id)->count();
        return [
            'total_results' => $totalCount,
            'per_page' => $limit,
            'pages_count' => ceil($totalCount / $limit)
        ];
    }

    /**
     * @param int $limit
     * @param int $page
     * @return mixed
     */
    public function getTransactions($limit = 20, $page = 1)
    {
        $offset = ($page - 1) * $limit;
        $query = "SELECT name, amount, order_amount, transaction_time, status, type, logo FROM (
            (
                SELECT s.name, us.cashback AS amount, us.order_amount, us.transaction_time, us.status, 'sale' AS type, logo
                FROM user_sales us
                LEFT JOIN stores s ON us.store_id = s.id
                WHERE us.user_id = {$this->user_id}
            )
            UNION ALL
            (
                SELECT b.name, ub.amount, null AS order_amount, ub.awarded_on AS transaction_time, ub.status, 'bonus' AS type, null AS logo
                FROM user_bonus ub
                LEFT JOIN bonus_types b ON b.code = ub.bonus_code
                WHERE ub.user_id = {$this->user_id}
            )
        ) AS transactions
        ORDER BY transaction_time DESC LIMIT {$offset},{$limit}";
        return DB::select($query);
    }

    public function getWalletTransactions($currency_code)
    {
        if ($currency_code == 'USD')
            $amountField = 'amount';
        else
            $amountField = 'amount_' . strtolower($currency_code);
        $query = "SELECT id,name, amount, transaction_time, type,expiration_date FROM (
            (
                SELECT us.id,s.name, us.cashback AS amount, us.transaction_time, 'sale' AS type,us.expiration_date
                FROM user_sales us
                LEFT JOIN stores s ON us.store_id = s.id
                WHERE us.user_id = {$this->user_id} and us.status='confirmed'
            )
            UNION ALL
            (
                SELECT ub.id,b.name, ub.amount, ub.awarded_on AS transaction_time, 'bonus' AS type,ub.expiration_date
                FROM user_bonus ub
                LEFT JOIN bonus_types b ON b.code = ub.bonus_code
                WHERE ub.user_id = {$this->user_id} and ub.status='confirmed'
            )
             UNION ALL
            (
                SELECT unb.id, nb.name, unb.amount, unb.awarded_on AS transaction_time, 'new_bonus' AS type, unb.expiration_date
                FROM user_new_bonuses unb
                LEFT JOIN new_bonus_types nb ON nb.id = unb.bonus_type_id
                WHERE unb.user_id = {$this->user_id} and unb.status='confirmed'
            )
             UNION ALL
            (
                SELECT up.id, pm.name, up.amount, up.paid_at AS transaction_time, 'payout' AS type, CURDATE() + INTERVAL 1 DAY as expiration_date
                FROM user_payouts up
                LEFT JOIN payout_methods pm ON pm.code = up.method_code
                WHERE up.user_id = {$this->user_id} and up.status = 'completed'
            )
              UNION ALL
            (
                SELECT uea.id, null as name, uea." . $amountField . " as amount, uea.created_at AS transaction_time, 'expired' AS type, null as expiration_date
                FROM user_expired_available_amount uea
                WHERE uea.user_id = {$this->user_id}
            )
        ) AS transactions
        ORDER BY transaction_time DESC";
        return DB::select($query);
    }

    /**
     * @return array|int[]
     */
    public function getWalletBalance()
    {
        $balance = $this->getBalances();
        $totalAmount = $balance['cashback']['confirmed'] + $balance['reward']['confirmed'] - $balance['paid']['amount'];
        $totalConfirmed = $balance['cashback']['confirmed'] + $balance['reward']['confirmed'] - $balance['paid']['amount'];
        $totalPending = $balance['cashback']['pending'] + $balance['reward']['pending'];
        $bonusEarned = $balance['reward']['confirmed'] + $balance['reward']['pending'];

        return [
            'balance' => $totalAmount,
            'pending' => $totalPending,
            'available' => $totalConfirmed,
            'bonus' => $bonusEarned
        ];
    }

    /**
     * @param int $limit
     * @param int $page
     * @return mixed
     */
    public function getWalletPayouts($limit = null, $page = 1)
    {
        return $this->payouts()
            ->with([
                'method_info' => function ($q) {
                    $q->withoutGlobalScopes()->selectRaw(PayoutMethods::colSelect());
                }
            ])
            ->orderBy('id', 'DESC')
            ->paginate(null);
    }

    /**
     * @param int $limit
     * @param int $page
     * @return mixed
     */
    public function getWalletClicks($limit = 20, $page = 1)
    {
        return $this->clicks()
            ->with([
                'store' => function ($q) {
                    $q->withoutGlobalScopes()->selectRaw(Store::colSelect());
                }
            ])
            ->OrderByClick()
            ->paginate($limit, ['*'], 'page', $page);
    }

    /**
     * @return mixed
     */
    public function getFavoriteStoresCount()
    {
        return $this->favourites()
            ->join('stores', function ($join) {
                $join->on('user_favs.object_id', '=', 'stores.id');
                $regionCode = request('region');
                if ($regionCode) {
                    $join->whereJsonContains("region_code", $regionCode);
                }
            })
            ->where('fav_object', '=', 'store')
            ->count();
    }

    /**
     * @return string[]
     */
    public static function getDeclinedBonusCodes()
    {
        return self::$declinedBonusCodes;
    }

    /**
     * @param array $merchants_user_ids
     * @param int $batch_count
     */
    public static function declineMerchantBonuses($merchants_user_ids = [], $batch_count = 50)
    {
        $query = UserBonus::whereIn('bonus_code', self::getDeclinedBonusCodes())->whereIn('user_id', $merchants_user_ids);

        if ($merchants_user_ids) {
            $bonusesCount = $query->count();
            $num_of_batches = ceil($bonusesCount / $batch_count);
            for ($i = 0; $i < $num_of_batches; $i++) {
                $bonuses = $query->skip($i * $batch_count)->take($batch_count)->get();
                foreach ($bonuses as $bonus) {
                    $bonus->status = 'declined';
                    $bonus->save();
                }
            }
        }
    }


    // TODO: update for new attr for merchant

    /**
     * @param array $merchants_user_ids
     * @param int $batch_count
     */
    public static function markUsersAsMerchants(array $merchants_user_ids, $batch_count = 50)
    {
        if ($merchants_user_ids) {
            $usersCount = count($merchants_user_ids);
            $num_of_batches = ceil($usersCount / $batch_count);
            for ($i = 0; $i < $num_of_batches; $i++) {
                $merchants = self::whereIn('user_id', $merchants_user_ids)->skip($i * $batch_count)->take($batch_count)->get();
                foreach ($merchants as $merchant) {
                    $merchant->is_merchant = 1;
                    $merchant->save();
                }
            }
        }
    }

    /**
     * @param $value
     * @return int
     */
    public function getPendingBalanceAttribute($value)
    {
        if ($value < 0)
            return 0;
        else
            return $value;
    }

    /**
     * @param $value
     * @return int
     */
    public function getConfirmedBalanceAttribute($value)
    {
        if ($value < 0)
            return 0;
        else
            return $value;
    }

    /**
     *
     */
    public function delete()
    {
        $this->active = -1;
        $this->email = 'DELETED_' . time() . '_' . $this->email;
        $this->phone_number = null;
        $this->save();
    }

    /**
     * @param int $limit
     * @param int $page
     * @return mixed
     */
    public function getNotifications($limit = 20, $page = 1)
    {
        return $this->notifications()->paginate($limit, ['*'], 'page', $page);
    }


    /**
     * @param $currencyRateCollection
     * @param $currencyCode
     * @param $dateTime
     * @return mixed
     */
    public static function getCurrencyRate($currencyRateCollection, $currencyCode, $dateTime)
    {
        $currencyRate = $currencyRateCollection->where('start_date', '<', $dateTime)
            ->where('currency_code', $currencyCode)
            ->sortByDesc('start_date')->first();
        if (is_null($currencyRate)) $currencyRate = $currencyRateCollection->where('currency_code', $currencyCode)->first();
        return $currencyRate;
    }


    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function likedCoupons()
    {
        return $this->belongsToMany(Coupon::class, 'coupon_user' ,'user_id')->where('reaction', 'like');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function dislikedCoupons()
    {
        return $this->belongsToMany(Coupon::class, 'coupon_user','user_id' )->where('reaction', 'dislike');
    }

    /**
     * @throws CurrencyNotFoundException
     */
    public function currenciesConversionsRate(string $transactionTime): array
    {
        $currencyRates = CurrencyRate::query()
            ->select('currency_code', 'conversion_rate')
            ->whereIn('currency_code', ['EGP', 'SAR', 'AED', 'KWD', 'USD'])
            ->whereDate('start_date', date('Y-m-d', strtotime($transactionTime)))
            ->groupBy(['currency_code'])
            ->pluck('conversion_rate', 'currency_code')
            ->toArray();
         if(count($currencyRates) == 0){
             throw new CurrencyNotFoundException();
         }
         return $currencyRates;
    }
}