<?php

namespace App\Http\Controllers;

use App\Http\Requests\CreateOnlinePaymentRequest;
use App\Models\Account;
use App\Models\Client;
use App\Models\GatewayType;
use App\Models\Invitation;
use App\Models\Payment;
use App\Models\PaymentMethod;
use App\Models\Product;
use App\Ninja\Mailers\UserMailer;
use App\Ninja\PaymentDrivers\PaymentActionRequiredException;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\InvoiceRepository;
use App\Services\InvoiceService;
use App\Services\PaymentService;
use Auth;
use Crawler;
use Exception;
use Session;
use URL;
use Utils;
use Validator;
use View;
use Request;

/**
 * Class OnlinePaymentController.
 */
class OnlinePaymentController extends BaseController
{
    /**
     * @var PaymentService
     */
    protected $paymentService;

    /**
     * @var UserMailer
     */
    protected $userMailer;

    /**
     * @var InvoiceRepository
     */
    protected $invoiceRepo;

    /**
     * OnlinePaymentController constructor.
     *
     * @param PaymentService $paymentService
     * @param UserMailer     $userMailer
     */
    public function __construct(PaymentService $paymentService, UserMailer $userMailer, InvoiceRepository $invoiceRepo)
    {
        $this->paymentService = $paymentService;
        $this->userMailer = $userMailer;
        $this->invoiceRepo = $invoiceRepo;
    }

    /**
     * @param $invitationKey
     * @param bool  $gatewayType
     * @param bool  $sourceId
     * @param mixed $gatewayTypeAlias
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function showPayment($invitationKey, $gatewayTypeAlias = false, $sourceId = false)
    {
        if (! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
            return response()->view('error', [
                'error' => trans('texts.invoice_not_found'),
                'hideHeader' => true,
            ]);
        }

        if (! request()->capture && ! $invitation->invoice->canBePaid()) {
            return redirect()->to('view/' . $invitation->invitation_key);
        }

        $invitation = $invitation->load('invoice.client.account.account_gateways.gateway');
        $account = $invitation->account;

        if (! request()->capture && $account->requiresAuthorization($invitation->invoice) && ! session('authorized:' . $invitation->invitation_key)) {
            return redirect()->to('view/' . $invitation->invitation_key);
        }

        $account->loadLocalizationSettings($invitation->invoice->client);

        if (! $gatewayTypeAlias) {
            $gatewayTypeId = Session::get($invitation->id . 'gateway_type');
        } elseif ($gatewayTypeAlias != GATEWAY_TYPE_TOKEN) {
            $gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias);
        } else {
            $gatewayTypeId = $gatewayTypeAlias;
        }

        $paymentDriver = $account->paymentDriver($invitation, $gatewayTypeId);

        if (! $paymentDriver) {
            return redirect()->to('view/' . $invitation->invitation_key);
        }

        // add a delay check for token links
        if ($gatewayTypeId == GATEWAY_TYPE_TOKEN) {
            $key = 'payment_token:' . $invitation->invitation_key;
            if (cache($key)) {
                return redirect()->to('view/' . $invitation->invitation_key);
            } else {
                cache([$key => true], \Carbon::now()->addSeconds(10));
            }
        }

        try {
            return $paymentDriver->startPurchase(Request::all(), $sourceId);
        } catch (Exception $exception) {
            return $this->error($paymentDriver, $exception);
        }
    }

    /**
     * @param CreateOnlinePaymentRequest $request
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function doPayment(
        CreateOnlinePaymentRequest $request,
        $invitationKey,
        $gatewayTypeAlias = false,
        $sourceId = false
    )
    {
        $invitation = $request->invitation;

        if ($gatewayTypeAlias == GATEWAY_TYPE_TOKEN) {
            $gatewayTypeId = $gatewayTypeAlias;
        } elseif ($gatewayTypeAlias) {
            $gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias);
        } else {
            $gatewayTypeId = Session::get($invitation->id . 'gateway_type');
        }

        $paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayTypeId);

        if (! $invitation->invoice->canBePaid() && ! request()->capture) {
            return redirect()->to('view/' . $invitation->invitation_key);
        }

        try {
            // Load the payment method to charge.
            // Currently only hit for saved cards that still require 3D secure verification.
            $paymentMethod = null;
            if ($sourceId) {
                $paymentMethod = PaymentMethod::clientId($invitation->invoice->client_id)
                                              ->wherePublicId($sourceId)
                                              ->firstOrFail();
            }

            $paymentDriver->completeOnsitePurchase($request->all(), $paymentMethod);

            if (request()->capture) {
                return redirect('/client/dashboard')->withMessage(trans('texts.updated_payment_details'));
            } elseif ($paymentDriver->isTwoStep()) {
                Session::flash('warning', trans('texts.bank_account_verification_next_steps'));
            } else {
                Session::flash('message', trans('texts.applied_payment'));
            }

            return $this->completePurchase($invitation);
        } catch (PaymentActionRequiredException $exception) {
            return $paymentDriver->startStepTwo($exception->getData());
        } catch (Exception $exception) {
            return $this->error($paymentDriver, $exception, true);
        }
    }

    /**
     * @param bool  $invitationKey
     * @param mixed $gatewayTypeAlias
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function offsitePayment($invitationKey = false, $gatewayTypeAlias = false)
    {
        if (Crawler::isCrawler()) {
            return redirect()->to(NINJA_WEB_URL, 301);
        }

        $invitationKey = $invitationKey ?: Session::get('invitation_key');
        $invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')
                        ->where('invitation_key', '=', $invitationKey)->firstOrFail();

        if (! $gatewayTypeAlias) {
            $gatewayTypeId = Session::get($invitation->id . 'gateway_type');
        } elseif ($gatewayTypeAlias != GATEWAY_TYPE_TOKEN) {
            $gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias);
        } else {
            $gatewayTypeId = $gatewayTypeAlias;
        }

        $paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayTypeId);

        if ($error = \Request::input('error_description') ?: \Request::input('error')) {
            return $this->error($paymentDriver, $error);
        }

        try {
            if ($paymentDriver->completeOffsitePurchase(Request::all())) {
                Session::flash('message', trans('texts.applied_payment'));
            }

            return $this->completePurchase($invitation, true);
        } catch (Exception $exception) {
            return $this->error($paymentDriver, $exception);
        }
    }

    private function completePurchase($invitation, $isOffsite = false)
    {
        if (request()->wantsJson()) {
            return response()->json(RESULT_SUCCESS);
        } elseif ($redirectUrl = session('redirect_url:' . $invitation->invitation_key)) {
            $separator = strpos($redirectUrl, '?') === false ? '?' : '&';

            return redirect()->to($redirectUrl . $separator . 'invoice_id=' . $invitation->invoice->public_id);
        } else {
            // Allow redirecting to iFrame for offsite payments
            if ($isOffsite) {
                return redirect()->to($invitation->getLink());
            } else {
                return redirect()->to('view/' . $invitation->invitation_key);
            }
        }
    }

    public function completeSource($invitationKey, $gatewayType)
    {
        if (! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
            return response()->view('error', [
                'error' => trans('texts.invoice_not_found'),
                'hideHeader' => true,
            ]);
        }

        return redirect()->to('view/' . $invitation->invitation_key);
    }

    /**
     * @param $paymentDriver
     * @param $exception
     * @param bool $showPayment
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    private function error($paymentDriver, $exception, $showPayment = false)
    {
        if (is_string($exception)) {
            $displayError = $exception;
            $logError = $exception;
        } else {
            $displayError = $exception->getMessage();
            $logError = Utils::getErrorString($exception);
        }

        $message = sprintf('%s: %s', ucwords($paymentDriver->providerName()), $displayError);
        Session::flash('error', $message);

        $message = sprintf('Payment Error [%s]: %s', $paymentDriver->providerName(), $logError);
        Utils::logError($message, 'PHP', true);

        $route = $showPayment ? 'payment/' : 'view/';

        return redirect()->to($route . $paymentDriver->invitation->invitation_key);
    }

    /**
     * @param $routingNumber
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function getBankInfo($routingNumber)
    {
        if (strlen($routingNumber) != 9 || ! preg_match('/\d{9}/', $routingNumber)) {
            return response()->json([
                'message' => 'Invalid routing number',
            ], 400);
        }

        $data = PaymentMethod::lookupBankData($routingNumber);

        if (is_string($data)) {
            return response()->json([
                'message' => $data,
            ], 500);
        } elseif (! empty($data)) {
            return response()->json($data);
        }

        return response()->json([
            'message' => 'Bank not found',
        ], 404);
    }

    /**
     * @param $accountKey
     * @param $gatewayId
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function handlePaymentWebhook($accountKey, $gatewayId)
    {
        $gatewayId = intval($gatewayId);

        $account = Account::where('accounts.account_key', '=', $accountKey)->first();

        if (! $account) {
            return response()->json([
                'message' => 'Unknown account',
            ], 404);
        }

        $accountGateway = $account->getGatewayConfig(intval($gatewayId));

        if (! $accountGateway) {
            return response()->json([
                'message' => 'Unknown gateway',
            ], 404);
        }

        $paymentDriver = $accountGateway->paymentDriver();

        try {
            $result = $paymentDriver->handleWebHook(Request::all());

            return response()->json(['message' => $result]);
        } catch (Exception $exception) {
            if (! Utils::isNinjaProd()) {
                Utils::logError($exception->getMessage(), 'HOOK');
            }

            return response()->json(['message' => $exception->getMessage()], 500);
        }
    }

    public function handleBuyNow(ClientRepository $clientRepo, InvoiceService $invoiceService, $gatewayTypeAlias = false)
    {
        if (Crawler::isCrawler()) {
            return redirect()->to(NINJA_WEB_URL, 301);
        }

        $account = Account::whereAccountKey(\Request::input('account_key'))->first();
        $redirectUrl = \Request::input('redirect_url');
        $failureUrl = URL::previous();

        if (! $account || ! $account->enable_buy_now_buttons || ! $account->hasFeature(FEATURE_BUY_NOW_BUTTONS)) {
            return redirect()->to("{$failureUrl}/?error=invalid account");
        }

        Auth::onceUsingId($account->users[0]->id);
        $account->loadLocalizationSettings();
        $product = Product::scope(\Request::input('product_id'))->first();

        if (! $product) {
            return redirect()->to("{$failureUrl}/?error=invalid product");
        }

        // check for existing client using contact_key
        $client = false;
        if ($contactKey = \Request::input('contact_key')) {
            $client = Client::scope()->whereHas('contacts', function ($query) use ($contactKey) {
                $query->where('contact_key', $contactKey);
            })->first();
        }
        if (! $client) {
            $rules = [
                'first_name' => 'string|max:100',
                'last_name' => 'string|max:100',
                'email' => 'email|string|max:100',
            ];

            $validator = Validator::make(Request::all(), $rules);
            if ($validator->fails()) {
                return redirect()->to("{$failureUrl}/?error=" . $validator->errors()->first());
            }

            $data = request()->all();
            $data['currency_id'] = $account->currency_id;
            $data['custom_value1'] = request()->custom_client1;
            $data['custom_value2'] = request()->custom_client2;
            $data['contact'] = request()->all();
            $data['contact']['custom_value1'] = request()->custom_contact1;
            $data['contact']['custom_value2'] = request()->custom_contact2;

            if (request()->currency_code) {
                $data['currency_code'] = request()->currency_code;
            }
            if (request()->country_code) {
                $data['country_code'] = request()->country_code;
            }
            $client = $clientRepo->save($data, $client);
        }

        $data = [
            'client_id' => $client->id,
            'is_recurring' => filter_var(\Request::input('is_recurring'), FILTER_VALIDATE_BOOLEAN),
            'is_public' => filter_var(\Request::input('is_recurring'), FILTER_VALIDATE_BOOLEAN),
            'frequency_id' => \Request::input('frequency_id'),
            'auto_bill_id' => \Request::input('auto_bill_id'),
            'start_date' => \Request::input('start_date', date('Y-m-d')),
            'tax_rate1' => $account->tax_rate1,
            'tax_name1' => $account->tax_name1 ?: '',
            'tax_rate2' => $account->tax_rate2,
            'tax_name2' => $account->tax_name2 ?: '',
            'custom_text_value1' => \Request::input('custom_invoice1'),
            'custom_text_value2' => \Request::input('custom_invoice2'),
            'invoice_items' => [[
                'product_key' => $product->product_key,
                'notes' => $product->notes,
                'cost' => $product->cost,
                'qty' => request()->quantity ?: (request()->qty ?: 1),
                'tax_rate1' => $product->tax_rate1,
                'tax_name1' => $product->tax_name1 ?: '',
                'tax_rate2' => $product->tax_rate2,
                'tax_name2' => $product->tax_name2 ?: '',
                'custom_value1' => \Request::input('custom_product1') ?: $product->custom_value1,
                'custom_value2' => \Request::input('custom_product2') ?: $product->custom_value2,
            ]],
        ];
        $invoice = $invoiceService->save($data);
        if ($invoice->is_recurring) {
            $invoice = $this->invoiceRepo->createRecurringInvoice($invoice->fresh());
        }
        $invitation = $invoice->invitations[0];
        $link = $invitation->getLink();

        if ($redirectUrl) {
            session(['redirect_url:' . $invitation->invitation_key => $redirectUrl]);
        }

        if ($gatewayTypeAlias) {
            $link = $invitation->getLink('payment') . "/{$gatewayTypeAlias}";
        } else {
            $link = $invitation->getLink();
        }

        if (filter_var(\Request::input('return_link'), FILTER_VALIDATE_BOOLEAN)) {
            return $link;
        } else {
            return redirect()->to($link);
        }
    }

    public function showAppleMerchantId()
    {
        if (Utils::isNinja()) {
            $subdomain = Utils::getSubdomain(\Request::server('HTTP_HOST'));
            if (! $subdomain || $subdomain == 'app') {
                exit('Invalid subdomain');
            }
            $account = Account::whereSubdomain($subdomain)->first();
        } else {
            $account = Account::first();
        }

        if (! $account) {
            exit('Account not found');
        }

        $accountGateway = $account->account_gateways()
            ->whereGatewayId(GATEWAY_STRIPE)->first();

        if (! $account) {
            exit('Apple merchant id not set');
        }

        echo $accountGateway->getConfigField('appleMerchantId');
        exit;
    }
}
