<?php

namespace App\Http\Controllers;

use App\Models\Userpayoutrequests;
use App\Traits\NotDeletableTrait;
use Dotenv\Exception\ValidationException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Validator;
use Yajny\EventsYj\Events\PayoutNew;
use Yajny\EventsYj\Events\PayoutUpdated;
use Yajny\EventsYj\Library\PayMob\EWalletPayout;
use Yajny\Member\Controllers\PayoutCtrl;
use Yajny\Member\Entities\PayoutAddRequest;
use Yajny\Member\Entities\PayoutMethodFeesTypeUtil;
use Yajny\Member\Entities\Payouts\PayoutStatusUtil;
use Yajny\Member\Entities\PayoutUpdateRequest;
use Yajny\Member\Exceptions\UserPayouts\AmountLessThanMinAmountException;
use Yajny\Member\Exceptions\UserPayouts\AmountMoreThanConfirmedBalanceException;
use Yajny\Member\Exceptions\UserPayouts\PaymentAccountNotFoundException;
use Yajny\Member\Exceptions\UserPayouts\PaymentMethodNotFoundException;
use Yajny\Member\Models\Currencies;
use Yajny\Member\Models\Member;
use Yajny\Member\Models\PayoutMethods;
use Yajny\Member\Models\UserPayouts;
use Yajny\Member\Models\UsersBalance;

class UserpayoutrequestsController extends DataManangeCtrl
{
    use NotDeletableTrait;

    protected $layout = "layouts.main";
    protected $data = array();
    public $module = 'userpayoutrequests';
    static $per_page = '10';
    public $allCurrencies;

    public function __construct()
    {
        $this->allCurrencies = Currencies::all()->mapWithKeys(function ($currency) {
            return [$currency->iso_code => $currency];
        });

        $this->model = new Userpayoutrequests();
        parent::__construct();
    }

    /**
     * @param Request $request
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
     */
    public function index(Request $request)
    {
        $view = parent::index($request);
        $payMobBalance = 0;
        try{
            $payMobBalance = (new EWalletPayout())->getBalance();
        }catch(\Exception $e){

        }
        return $view->with('payMobBalance', $payMobBalance);
    }

    /**
     * @param Request $request
     * @param int $id
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
     */
    public function create(Request $request, $id = 0)
    {
        $methods = PayoutMethods::withoutGlobalScopes()->get()->keyBy('code');
        return view('manage.userpayoutrequests.form', [
            'row' => (new Userpayoutrequests())->toArray(),
            'methods' => $methods->toArray(),
            'allCurrencies' => $this->allCurrencies
        ]);
    }

    /**
     * @param Request $request
     * @param $id
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
     */
    public function edit(Request $request, $id)
    {
        $userPayout = Userpayoutrequests::findOrFail($id);
        $methods = PayoutMethods::withoutGlobalScopes()->get()->keyBy('code');

        $user = Member::with([
            'payout_methods_without_global_scopes' => function ($query) use ($userPayout) {
                $query->with(['info' => function ($q) {
                    $q->withoutGlobalScopes();
                }]);
            }
        ])->where('user_id', '=', $userPayout->user_id)->first();

        return view('manage.userpayoutrequests.form', [
            'row' => $userPayout->toArray(),
            'methods' => $methods->toArray(),
            'allCurrencies' => $this->allCurrencies,
            'userData' => $this->formatPayoutsForForm([$user], $userPayout->account)
        ]);
    }

    /**
     * @param Request $request
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    function store(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'id' => 'nullable|exists:user_payout_methods,id',
            'user_id' => 'required|exists:users,user_id',
            'method_code' => 'required|exists:payout_methods,code',
            'account' => "required|exists:user_payout_methods,account,user_id,{$request->user_id}",
            'amount' => 'required|numeric',
            'status' => 'required_with:id|in:' . implode(',', PayoutStatusUtil::getStatuses()),
            'note' => 'nullable'
        ]);

        try {
            $validator->validate();
            if ($request->has('id')) {
                $payout_id = static::updatePayout($request->id, new PayoutUpdateRequest($request->user_id, $request->method_code, $request->account, $request->amount, $request->status, $request->note));
            } else {
                $payout_id = static::addPayout(new PayoutAddRequest($request->user_id, $request->method_code, $request->account, $request->amount, $request->note));
            }

            if (!is_null($request->input('apply')))
                return redirect('/manage/' . $this->module . '/' . $payout_id . '/edit?' . $this->returnUrl())
                    ->with('message', __('core.note_success'))->with('status', 'success');

            return redirect('/manage/' . $this->module . '?' . $this->returnUrl())
                ->with('message', __('core.note_success'))->with('status', 'success');
        } catch (ValidationException $e) {
            if ($request->has('id'))
                return redirect('/manage/' . $this->module . '/' . $request->id . '/edit')
                    ->with('message', __('core.note_error'))->with('status', 'error')
                    ->withErrors($validator)->withInput();
            else
                return redirect('/manage/' . $this->module . '/create/' . '?old=true&return=')
                    ->with('message', __('core.note_error'))->with('status', 'error')
                    ->withErrors($validator)->withInput();
        } catch (\Exception $exception) {
            return Redirect::back()
                ->with('message', $exception->getMessage())->with('status', 'error');
        }
    }

    /**
     * @throws AmountLessThanMinAmountException
     * @throws AmountMoreThanConfirmedBalanceException
     * @throws PaymentAccountNotFoundException
     * @throws PaymentMethodNotFoundException
     */
    public static function addPayout(PayoutAddRequest $request)
    {
        $user = Member::findOrFail($request->user_id);
        $method = PayoutMethods::where('code', $request->method_code)->where('enabled', true)->first();
        $amount = $request->amount;
        if (!$method) {
            throw new PaymentMethodNotFoundException();
        }

        $userPayMode = $user->payout_methods()->where('method_code', $request->method_code)->where('account', $request->account)->first();
        $conversionRate = Currencies::where('iso_code', $method->default_currency)->first()->conversion_rate;
        $amountInUSD = $amount / $conversionRate;


        if (!$userPayMode) {
            throw new PaymentAccountNotFoundException();
        }
        if ($amount < $method->minimum_amount) {
            throw new AmountLessThanMinAmountException();
        }


        $userBalance = UsersBalance::where('user_id', $user->user_id)->first();
        // method currency is USD
        if ($method->default_currency == "USD") {
            if ($userBalance->available_amount < $amountInUSD) {
                throw new AmountMoreThanConfirmedBalanceException();
            }
        } // payout method used is different from USD
        else {

            if ($userBalance['available_amount_' . strtolower($method->default_currency)] < $amount)
                throw new AmountMoreThanConfirmedBalanceException();
        }
        $fees = self::calculateFees($method, $request);
        $payout = new UserPayouts;
        self::savePayout($payout, $method, $request, $amountInUSD, $conversionRate, $fees);
        $user->payouts()->save($payout);

        event(new  PayoutNew($user, $payout));
        return $payout->id;
    }

    public static function updatePayout($id, PayoutUpdateRequest $request)
    {
        $user = Member::findOrFail($request->user_id);
        $userPayout = UserPayouts::findOrFail($id);

        $updateRate = false;
        $methodQuery = PayoutMethods::where('code', $request->method_code);
        if ($userPayout->method_code == $request->method_code) {
            // No Change in Method --> Keep Conversion Rate
            $method = $methodQuery->withoutGlobalScopes()->first();
        } else {
            // Change in Method --> Update Conversion Rate
            $method = $methodQuery->first();
            $updateRate = true;
        }

        if (!$method)
            throw new PaymentMethodNotFoundException();

        $userPayoutMethodQuery = $user->payout_methods_without_global_scopes()->where('method_code', $request->method_code)->where('account', $request->account);
        if ($userPayout->account != $request->account)
            $userPayoutMethodQuery = $userPayoutMethodQuery->where('enabled', 1);

        $userPayoutMethod = $userPayoutMethodQuery->first();
        if (!$userPayoutMethod)
            throw new PaymentAccountNotFoundException();

        $conversionRate = Currencies::where('iso_code', $method->default_currency)->first()->conversion_rate;
        $amountInUSD = $request->amount;
        if($method->default_currency!="USD") $amountInUSD = $request->amount/$conversionRate;

        if (in_array($request->status, PayoutStatusUtil::getAcceptingStatuses()) ){
            if ($method->default_currency!="USD"){
                if ($request->amount*$conversionRate < $method->minimum_amount)
                    throw new AmountLessThanMinAmountException();
            }else{
                if ($request->amount < $method->minimum_amount)
                    throw new AmountLessThanMinAmountException();
            }

        }
        $confirmed_balance = $user->confirmed_balance;
        if (!in_array($userPayout->status, PayoutStatusUtil::getRejectingStatuses()))
            $confirmed_balance += $userPayout->amount;
        if ($method->default_currency=="USD"){
            if ($amountInUSD > $confirmed_balance)
                throw new AmountMoreThanConfirmedBalanceException();
        }
        $fees = self::calculateFees($method, $request);

        self::savePayout($userPayout, $method, $request, $amountInUSD, $conversionRate, $fees);
        $userPayout->save();

        event(new PayoutUpdated($user, $userPayout));
        return $userPayout->id;
    }

    public static function calculateFees(PayoutMethods $method, $request)
    {
        $fees = null;
        $methodFee = $method->fee;
        if ($methodFee) {
            if ($request->user_id) {
                $user = Member::find($request->user_id);
                if ($user->is_merchant) {
                    $methodFee->amount = $methodFee->merchant_amount;
                }
            }
            if ($methodFee->type == PayoutMethodFeesTypeUtil::PERCENTAGE)
                $fees = $request->amount * $methodFee->amount / 100;
            else
                $fees = $methodFee->amount;

            if ($methodFee->min_amount)
                $fees = max($fees, $methodFee->min_amount);
        }
        if($fees > 200){
           $fees = 200;
        }
        if($fees < 20){
            $fees = 20;
        }
        return $fees;
    }
    public static function savePayout(UserPayouts $payout, PayoutMethods $method, $request, $amountInUSD, $conversionRate, $fees = null)
    {

        $payout->method_code = $request->method_code;
        $payout->amount = $amountInUSD;
        $payout->final_amount = $amountInUSD;
        $payout->cashback_amount = 0;
        $payout->reward_amount = 0;
        $payout->account = $request->account;
        $payout->currency_rate = $conversionRate;
        $payout->local_currency = $method->default_currency;
        $payout->local_amount = $request->amount;
        $payout->local_final_amount = $request->amount;
        $payout->status = $request->status ?? PayoutStatusUtil::CREATED;
        if ($request->note)
            $payout->note = $request->note;

        if ($fees) {
            $feesInUSD = $fees / $conversionRate;
            $payout->fees_amount = $feesInUSD;
            $payout->local_fees_amount = $fees;
            $payout->final_amount = $amountInUSD - $feesInUSD;
            $payout->local_final_amount = $request->amount - $fees;
            $payout->fees_data = json_encode([
                'amount' => $method->fee->amount,
                'type' => $method->fee->type,
                'min_amount' => $method->fee->min_amount
            ]);
        }
    }

    /**
     * @param Request $request
     * @return array
     */
    public function filterMethodsByUser(Request $request)
    {
        if (!$request->has('search'))
            return ['items' => [], 'page' => $request->page, 'pagination' => true];

        $users = Member::with([
            'payout_methods_without_global_scopes' => function ($query) {
                $query->with(['info' => function ($q) {
                    $q->withoutGlobalScopes();
                }]);
            }
        ])
            ->orWhere('user_id', 'like', "%{$request->search}%")
            ->orWhere('first_name', 'like', "%{$request->search}%")
            ->orWhere('last_name', 'like', "%{$request->search}%")
            ->orWhere('email', 'like', "%{$request->search}%")
            ->orWhere('phone_number', 'like', "%{$request->search}%")
            ->simplePaginate();

        if ($users) {
             $items=[];
             $data= $this->formatPayoutsForForm($users->items());


            foreach($data as $item){
                if(!empty($item['user'])){
                    $items[]= $item;
                } ;
            }

            return ['items' => $items, 'page' => $request->page, 'pagination' => true];
        } else
            return ['items' => [], 'page' => $request->page, 'pagination' => true];
    }

    /**
     * @param array $users
     * @param null $selected_account
     * @return array
     */
    private function formatPayoutsForForm(array $users, $selected_account = null)
    {

        $items = [];

        foreach ($users as $user) {

            $userBalance = UsersBalance::where('user_id', $user->user_id)->first();

            $item = [];
            if ($user->payout_methods_without_global_scopes) {
                $available_accounts = [];
                $available_methods = [];
                $available_methods_fees = [];



                foreach ($user->payout_methods_without_global_scopes as $payout_method) {

                    if($payout_method->info->default_currency=='USD'){
                        $confirmed_balance = $userBalance['available_amount'];
                    }else{
                        $confirmed_balance = $userBalance['available_amount_' . strtolower($payout_method->info->default_currency)];
                    }

                    $item['user'] = [
                        'id' => $user->user_id,
                        'text' => "{$user->first_name} {$user->last_name} ({$user->email})",
                        'confirmed_balance' => (double)$confirmed_balance,
                        'available_amount_usd' => (double)$userBalance['available_amount'],
                        'available_amount_egp' => (double)$userBalance['available_amount_egp' ],
                        'default_currency' => $payout_method->info->default_currency
                    ];

                    $item['user']['balance_subaccount_usd']=0;
                    $item['user']['balance_subaccount_egp']=0;
                    $item['user']['total_with_subaccount_usd']=0;
                    $item['user']['total_with_subaccount_egp']=0;
                    if($user->is_merchant){
                        $moderators = $user->moderators ;
                        $balance_subaccount_usd=0;
                        $balance_subaccount_egp=0;
                        foreach($moderators  as $subuser) {
                             $userBalanceModer = UsersBalance::where('user_id', $subuser->user_id)->first();

                            if (!is_null($userBalanceModer)) {

                                    $balance_subaccount_usd += $userBalanceModer->available_amount ;
                                    $balance_subaccount_egp += $userBalanceModer->available_amount_egp ;

                            }
                        }
                        $item['user']['balance_subaccount_usd']=(double)$balance_subaccount_usd;
                        $item['user']['balance_subaccount_egp']=(double)$balance_subaccount_egp;

                        $item['user']['total_with_subaccount_usd']=(double)$item['user']['balance_subaccount_usd']+ (double)$userBalance['available_amount'];
                        $item['user']['total_with_subaccount_egp']=(double)$item['user']['balance_subaccount_egp']+ (double)$userBalance['available_amount_egp'];


                    }



                    $methodMinAmount = $payout_method->info->minimum_amount;

                    if ($payout_method->info->default_currency != "USD") {
                        $currency = $this->allCurrencies[$payout_method->info->default_currency];
                        $methodMinAmount = round($methodMinAmount / $currency->conversion_rate, 2);
                    }

                    $method = PayoutMethods::where('code',$payout_method->method_code)->where('enabled', true)->first();

                    if (!empty($method)) {
                        if (
                            ($methodMinAmount > 0 && $confirmed_balance >= $methodMinAmount) ||
                            ($selected_account && $selected_account == $payout_method->account)
                        ) {
                            $available_methods[$payout_method->method_code] = [
                                'id' => $payout_method->method_code,
                                'text' => $payout_method->info->name,
                                'currency' => $payout_method->info->default_currency,
                                'selected' => $selected_account && $selected_account == $payout_method->account
                            ];
                        }
                    }

                    if (!empty($method)){
                              $available_accounts[$payout_method->method_code][] = [
                                'id' => $payout_method->account,
                                'text' => "{$payout_method->name} ({$payout_method->account})",
                                'selected' => $selected_account && $selected_account == $payout_method->account,
                                'verified_at' =>  $payout_method->verified_at
                             ];

                    }

                    if ($payout_method->info->fee && !empty($method) && !isset($available_methods_fees[$payout_method->method_code])) {
                        $available_methods_fees[$payout_method->method_code] = [
                            'fee_type' => $payout_method->info->fee->type,
                            'fee_amount' => ($user->is_merchant) ? $payout_method->info->fee->merchant_amount : $payout_method->info->fee->amount,
                            'fee_min_amount' => ($payout_method->info->fee->min_amount) ? $payout_method->info->fee->min_amount : 0
                        ];
                    }
                }

                $item['available_methods'] = $available_methods;
                $item['available_accounts'] = $available_accounts;
                $item['methods_fees'] = $available_methods_fees;
            }

            $items[] = $item;

        }
        return $items;
    }
}
