<?php

namespace App\Http\Controllers\Web\Client;

use App\Http\Controllers\Controller;
use App\Http\Requests\Web\BankAccount\Rules\BankPhoneNumberRule;
use App\Http\Requests\Web\BankAccount\Rules\BankWithdrawAmountRule;
use App\Http\Requests\Web\BankAccount\Rules\SsnRule;
use App\Http\Requests\Web\BankAccount\Rules\StateRule;
use App\Models\Withdrawal\FiatWithdrawal;
use App\Mail\Withdrawals\WithdrawalConfirmed;
use App\Mail\Withdrawals\WithdrawalRejected;
use App\Models\Deposit\FiatDeposit;
use App\Mail\Deposits\AdminDepositReceived;
use App\Mail\Deposits\DepositReceived;
use App\Repositories\Currency\CurrencyRepository;
use App\Repositories\Deposit\FiatDepositRepository;
use App\Repositories\Wallet\WalletRepository;
use App\Repositories\Withdrawal\FiatWithdrawalRepository;
use App\Services\Currency\CurrencyService;
use App\Services\PaymentGateways\Coin\Ethereum\Api\EthereumGateway;
use App\Services\PaymentGateways\Fiat\SilaMoney\Services\SilaMoneyService;
use App\Services\Wallet\WalletService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Silamoney\Client\Domain\PlaidTokenType;
use Silamoney\Client\Domain\SearchFilters;
use Silamoney\Client\Domain\User;
use App\Models\User\User as UserModel;
use Setting;

class BankAccountController extends Controller
{
    public function kyc(Request $request)
    {
        $request->validate([
            'first_name' => 'required|max:30|min:2',
            'last_name' => 'required|max:30|min:2',
            'phone_number' => ['required', new BankPhoneNumberRule()],
            'date_of_birth' => 'required|date_format:Y-m-d|before:-18 years',
            'ssn' => ['required', new SsnRule()],
            'street' => 'required|max:120',
            'city' => 'required|max:40',
            'postal' => 'required|digits:5|numeric',
            'state' => ['required', new StateRule()]
        ]);

        $user = auth()->user();

        $service = new SilaMoneyService();

        $eth = new EthereumGateway();
        $walletAddress = $eth->createEthAddress();
        $cryptoAddress = $walletAddress['address'];
        $cryptoPrivateKey = $walletAddress['private_key'];

        $userHandle = $user->referral_code . '.silamoney.eth';

        $datetime = new \DateTime();
        $birthDate = $datetime->createFromFormat('Y-m-d', $request->date_of_birth); // Only date part will be taken when sent to api

        // Create user object
        $silaUser = new User($userHandle, $request->first_name, $request->last_name, $request->street, null, $request->city, $request->state, $request->postal, $request->phone_number, $user->email, $request->ssn, $cryptoAddress, $birthDate);

        // Call the api
        $response = $service->client->register($silaUser);

        if ($response->getStatusCode() == 200) {

            $kycLevel = 'DEFAULT';
            $response = $service->client->requestKYC($userHandle, $cryptoPrivateKey, $kycLevel);

            $user->bank_verified_private_key = $cryptoPrivateKey;
            $user->bank_verified_public_key = $cryptoAddress;
            $user->bank_verified = KYC_BANK_PENDING;
            $user->bank_verified_uuid = $response->getData()->getReference();
            $user->bank_verified_kyc = json_encode([
                'first_name' => $request->first_name,
                'last_name' => $request->last_name,
                'phone_number' => $request->phone_number,
                'date_of_birth' => $request->date_of_birth,
                'ssn' => $request->ssn,
                'street' => $request->street,
                'city' => $request->city,
                'postal' => $request->postal,
                'state' => $request->state,
            ]);

            $user->update();

            return response()->redirectTo('/user/profile/bank_accounts');

        } else {

            if (strpos($response->getData()->message, 'is taken') !== false) {
                $message = __('This user already applied for KYC');
            } else {
                $message = __('Unexpected errors have been occured during submitting the form');
            }

            return response()->redirectTo('/user/profile/bank_accounts')->withErrors([
                'common' => $message
            ]);
        }
    }

    public function ipn(Request $request)
    {
        if($request->event_type == "kyc") {

            $reference = mb_strtoupper($request->event_details['entity']);

            $user = UserModel::where('referral_code', $reference)->where('bank_verified', '!=', 0)->first();

            $status = $request->event_details['outcome'];

            if($status == KYC_BANK_PASSED_TEXT) {
                $user->bank_verified = KYC_BANK_VERIFIED;
            } elseif($status == KYC_BANK_FAILED_TEXT) {
                $user->bank_verified = KYC_BANK_FAILED;
            }

            $user->update();
        }

        if($request->event_type == "transaction") {

            $reference = mb_strtoupper($request->event_details['entity']);
            $status = $request->event_details['outcome'];

            $user = UserModel::where('referral_code', $reference)->first();

            if(!$user) return response()->json(['success' => false]);

            if($status == "success") {

                if($request->event_details['transaction_type'] == "issue") {

                    $transaction = $request->event_details['transaction'];

                    if (FiatDeposit::where('note', $transaction)->first()) return response()->json(['success' => false]);

                    return DB::transaction(function () use ($request, $user, $transaction) {

                        $amount = math_formatter(math_divide($request->event_details['sila_amount'], 100), 2);

                        $walletRepository = new WalletRepository();
                        $walletService = new WalletService();
                        $currencyRepository = new CurrencyRepository();
                        $currencyService = new CurrencyService();

                        $currency = $currencyRepository->getCurrencyBySymbol('USD');

                        if (!$currency) return;

                        $currencyService->increase($currency, $amount);
                        $currency->wallet_balance_updated_at = Carbon::now();
                        $currency->update();

                        $fee = math_percentage($amount, $currency->deposit_fee);
                        $amountWithFee = math_sub($amount, $fee);

                        $depositId = generate_uuid();

                        $data = [
                            'deposit_id' => $depositId,
                            'type' => 'ach',
                            'user_id' => $user->id,
                            'currency_id' => $currency->id,
                            'amount' => $amount,
                            'fee' => $fee,
                            'status' => FIAT_DEPOSIT_CONFIRMED,
                            'approved_at' => Carbon::now(),
                            'note' => $transaction,
                        ];

                        $storedDeposit = (new FiatDepositRepository())->store($data);

                        // Increase user wallet
                        $wallet = $walletRepository->getWalletByCurrency($user->id, $currency->id);
                        $walletService->increase($wallet, $amountWithFee);

                        Mail::to($storedDeposit->user)->queue(new DepositReceived($storedDeposit->user, math_formatter($amountWithFee, $storedDeposit->currency->decimals), $storedDeposit->currency->symbol));

                        // Admin Email Notification
                        $adminEmail = Setting::get('notification.admin_email', false);
                        $notificationAllowed = Setting::get('notification.fiat_deposits', false);

                        if ($adminEmail && $notificationAllowed) {
                            $route = route('admin.reports.deposits.fiat') . "?search=" . $depositId;
                            Mail::to($adminEmail)->queue(new AdminDepositReceived(math_formatter($amountWithFee, $storedDeposit->currency->decimals), $storedDeposit->currency->symbol, $route));
                        }
                        // END Admin Email Notification

                        return response()->json(['success' => true]);

                    }, DB_REPEAT_AFTER_DEADLOCK);

                } elseif($request->event_details['transaction_type'] == "redeem") {

                    $withdrawal = FiatWithdrawal::where('note', $request->event_details['transaction'])->where('status', 'pending')->first();

                    if(!$withdrawal) return response()->json(['success' => true]);

                    return DB::transaction(function () use ($request, $user, $withdrawal) {

                        $amount = math_formatter(math_divide($request->event_details['sila_amount'], 100), 2);

                        $walletRepository = new WalletRepository();
                        $walletService = new WalletService();
                        $currencyRepository = new CurrencyRepository();

                        $currency = $currencyRepository->getCurrencyBySymbol('USD');

                        if (!$currency) return;

                        $wallet = $walletRepository->getWalletByCurrency($user->id, $currency->id);

                        $withdrawal->status = FIAT_WITHDRAWAL_CONFIRMED;
                        $withdrawal->save();

                        // Decrease user wallet
                        $walletService->decrease($wallet, $amount, 'withdraw');

                        Mail::to($user)->queue(new WithdrawalConfirmed($user, math_formatter($amount, $currency->decimals), $currency->symbol, ''));

                        return response()->json(['success' => true]);

                    }, DB_REPEAT_AFTER_DEADLOCK);

                }


            } elseif($status == "failed" && $request->event_details['transaction_type'] == "redeem") {

                $walletRepository = new WalletRepository();
                $currencyRepository = new CurrencyRepository();

                $currency = $currencyRepository->getCurrencyBySymbol('USD');

                $amount = math_formatter(math_divide($request->event_details['sila_amount'], 100), 2);

                if (!$currency) return response()->json(['success' => false]);

                $withdrawal = FiatWithdrawal::where('note', $request->event_details['transaction'])->where('status', 'pending')->first();

                if (!$withdrawal) return response()->json(['success' => false]);

                $withdrawal->status = FIAT_WITHDRAWAL_REJECTED;
                $withdrawal->rejected_at = Carbon::now();
                $withdrawal->save();

                // Decrease user wallet
                $wallet = $walletRepository->getWalletByCurrency($user->id, $currency->id);

                $walletService = new WalletService();
                $walletService->increase($wallet, $amount, 'wallet');
                $walletService->decrease($wallet, $amount, 'withdraw');

                Mail::to($user)->queue(new WithdrawalRejected($user, math_formatter($amount, $currency->decimals), $currency->symbol, __('Your withdraw request was failed. Please contact us to get more information')));

                return response()->json(['success' => true]);

            }
        }

        return response()->json(['success' => true]);
    }

    public function plaid_token() {

        $service = new SilaMoneyService();

        $user = auth()->user();

        $userHandle = $user->referral_code . '.silamoney.eth';

        $response = $service->client->plaidLinkToken($userHandle);

        if($response->getStatusCode() == STATUS_OK) {
            return response()->json(['token' => $response->getData()->getLinkToken()]);
        }

    }

    public function link_account(Request $request) {

        $service = new SilaMoneyService();

        $user = auth()->user();

        $userHandle = $user->referral_code . '.silamoney.eth';

        $accountName = $request->get('name');
        $plaidToken = $request->get('token');
        $accountId = $request->get('account_id');

        $plaidTokenType = PlaidTokenType::LINK(); // Optional.  PROCESSOR

        $response = $service->client->linkAccount($userHandle, $user->bank_verified_private_key, $plaidToken, $accountName, $accountId, $plaidTokenType);

        if($response->getStatusCode() == STATUS_OK) {
            return response()->json(['success' => true]);
        }

        return response()->json(['success' => false]);
    }

    public function delete_account(Request $request) {

        $service = new SilaMoneyService();

        $user = auth()->user();

        $userHandle = $user->referral_code . '.silamoney.eth';
        $accountName = $request->name;

        $response = $service->client->deleteAccount($userHandle, $accountName);

        if($response->getStatusCode() == STATUS_OK) {
            return response()->json(['success' => true]);
        }

        return response()->json(['success' => false]);
    }

    public function getAccounts() {

        $service = new SilaMoneyService();

        $user = auth()->user();

        $userHandle = $user->referral_code . '.silamoney.eth';

        $response = $service->client->getAccounts($userHandle, $user->bank_verified_private_key);

        if($response->getStatusCode() == STATUS_OK) {
            return response()->json(['accounts' => $response->getData()]);
        }

        return response()->json(['accounts' => []]);
    }

    public function getTransactions() {

        $service = new SilaMoneyService();

        $user = auth()->user();

        $userHandle = $user->referral_code . '.silamoney.eth';

        $filters = new SearchFilters();

        $response = $service->client->getTransactions($userHandle, $filters, $user->bank_verified_private_key);

        if($response->getStatusCode() == STATUS_OK) {
            return response()->json($response->getData());
        }

        return response()->json([])->setStatusCode(422);
    }

    public function issueSila(Request $request) {

        $request->validate([
            'amount' => ['required', 'numeric', 'min:1', $request->type == "withdraw" ? new BankWithdrawAmountRule() : null],
        ]);

        $service = new SilaMoneyService();

        $user = auth()->user();

        $userHandle = $user->referral_code . '.silamoney.eth';

        $amountInUsd = math_formatter($request->get('amount'), 2);
        $amount = math_multiply($amountInUsd, 100);

        $accountName = $request->get('account');

        $userPrivateKey = $user->bank_verified_private_key;

        if($request->type == "deposit") {
            $response = $service->client->issueSila($userHandle, $amount, $accountName, $userPrivateKey);
        } else {
            $response = $service->client->redeemSila($userHandle, $amount, $accountName, $userPrivateKey);
        }

        if($response->getStatusCode() == STATUS_OK) {

            if($request->type == "withdraw") {

                $data = [
                    'name' => 'ACH',
                    'iban' => 'ACH',
                    'swift' => 'ACH',
                    'ifsc' => 'ACH',
                    'address' => 'ACH',
                    'account_holder_name' => 'ACH',
                    'account_holder_address' => 'ACH',
                    'country_id' => 226,
                    'amount' => $amountInUsd,
                    'note' => $response->getData()->getTransactionId(),
                ];

                $currencyRepository = new CurrencyRepository();

                $currency = $currencyRepository->getCurrencyBySymbol('USD');

                $data['currency_id'] = $currency->id;

                $fee = math_percentage($amountInUsd, $currency->withdraw_fee);

                $data['user_id'] = auth()->user()->getAuthIdentifier();
                $data['status'] = FIAT_WITHDRAWAL_PENDING;
                $data['withdrawal_id'] = generate_uuid();
                $data['fee'] = $fee;

                (new FiatWithdrawalRepository())->processWithdraw($data, $currency);
            }

            return response()->json(['message' => __($response->getData()->getMessage())]);
        }

        return response()->json(['message' => false])->setStatusCode(422);
    }
}
