<?php
/**
 * Invoice Ninja (https://invoiceninja.com)
 *
 * @link https://github.com/invoiceninja/invoiceninja source repository
 *
 * @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
 *
 * @license https://opensource.org/licenses/AAL
 */

namespace App\PaymentDrivers;

use App\Events\Payment\PaymentWasCreated;
use App\Factory\PaymentFactory;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Stripe\Utilities;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Stripe\PaymentIntent;
use Stripe\SetupIntent;
use Stripe\Stripe;

class StripePaymentDriver extends BasePaymentDriver
{
    use MakesHash, Utilities;

    protected $refundable = true;

    protected $token_billing = true;

    protected $can_authorise_credit_card = true;

    protected $customer_reference = 'customerReferenceParam';

    protected $payment_method;

    /**
     * Methods in this class are divided into
     * two separate streams
     *
     * 1. Omnipay Specific
     * 2. Stripe Specific
     *
     * Our Stripe integration is deeper than
     * other gateways and therefore
     * relies on direct calls to the API
     */
    /************************************** Stripe API methods **********************************************************/

    /**
     * Initializes the Stripe API
     * @return void
     */
    public function init() :void
    {
        Stripe::setApiKey($this->company_gateway->getConfigField('apiKey'));
    }

    public function setPaymentMethod(string $method)
    {
        // Example: setPaymentMethod('App\\PaymentDrivers\\Stripe\\CreditCard');

        $this->payment_method = new $method($this);

        return $this;
    }

    /**
     * Returns the gateway types
     */
    public function gatewayTypes() :array
    {
        $types = [
            GatewayType::CREDIT_CARD,
            //GatewayType::TOKEN,
        ];

        if ($this->company_gateway->getSofortEnabled() && $this->invitation && $this->client() && isset($this->client()->country) && in_array($this->client()->country, ['AUT', 'BEL', 'DEU', 'ITA', 'NLD', 'ESP'])) {
            $types[] = GatewayType::SOFORT;
        }

        if ($this->company_gateway->getAchEnabled()) {
            $types[] = GatewayType::BANK_TRANSFER;
        }

        if ($this->company_gateway->getSepaEnabled()) {
            $types[] = GatewayType::SEPA;
        }

        if ($this->company_gateway->getBitcoinEnabled()) {
            $types[] = GatewayType::CRYPTO;
        }

        if ($this->company_gateway->getAlipayEnabled()) {
            $types[] = GatewayType::ALIPAY;
        }

        if ($this->company_gateway->getApplePayEnabled()) {
            $types[] = GatewayType::APPLE_PAY;
        }


        return $types;
    }

    public function viewForType($gateway_type_id)
    {
        switch ($gateway_type_id) {
            case GatewayType::CREDIT_CARD:
            case GatewayType::TOKEN:
                return 'gateways.stripe.credit_card';
                break;
            case GatewayType::SOFORT:
                return 'gateways.stripe.sofort';
                break;
            case GatewayType::BANK_TRANSFER:
                return 'gateways.stripe.ach';
                break;
            case GatewayType::SEPA:
                return 'gateways.stripe.sepa';
                break;
            case GatewayType::CRYPTO:
            case GatewayType::ALIPAY:
            case GatewayType::APPLE_PAY:
                return 'gateways.stripe.other';
                break;

            default:
                break;
        }
    }

    /**
     * Proxy method to pass the data into payment method authorizeView().
     * 
     * @param array $data 
     * 
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function authorizeView(array $data)
    {
        return $this->payment_method->authorizeView($data);
    }

    /**
     * Processes the gateway response for credit card authorization.
     *
     * @param \Illuminate\Http\Request $request The returning request object

     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function authorizeCreditCardResponse($request)
    {
        return $this->payment_method->authorizeResponse($request);
    }

    /**
     * Process the payment with gateway.
     *
     * @param array $data

     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|void
     */
    public function processPaymentView(array $data)
    {
        return $this->payment_method->paymentView($data);
    }

    /**
     * Payment Intent Reponse looks like this
      +"id": "pi_1FMR7JKmol8YQE9DuC4zMeN3"
      +"object": "payment_intent"
      +"allowed_source_types": array:1 [▼
        0 => "card"
      ]
      +"amount": 2372484
      +"canceled_at": null
      +"cancellation_reason": null
      +"capture_method": "automatic"
      +"client_secret": "pi_1FMR7JKmol8YQE9DuC4zMeN3_secret_J3yseWJG6uV0MmsrAT1FlUklV"
      +"confirmation_method": "automatic"
      +"created": 1569381877
      +"->currency()": "usd"
      +"description": "[3]"
      +"last_payment_error": null
      +"livemode": false
      +"next_action": null
      +"next_source_action": null
      +"payment_method": "pm_1FMR7ZKmol8YQE9DQWqPuyke"
      +"payment_method_types": array:1 [▶]
      +"receipt_email": null
      +"setup_future_usage": "off_session"
      +"shipping": null
      +"source": null
      +"status": "succeeded"
    */
    public function processPaymentResponse($request) //We never have to worry about unsuccessful payments as failures are handled at the front end for this driver.
    {
        return $this->payment_method->paymentResponse($request);
    }

    public function createPayment($data) :Payment
    {
        $payment = parent::createPayment($data);

        $client_contact = $this->getContact();
        $client_contact_id = $client_contact ? $client_contact->id : null;

        $payment->amount = $this->convertFromStripeAmount($data['amount'], $this->client->currency()->precision);
        $payment->type_id = $data['payment_type'];
        $payment->transaction_reference = $data['payment_method'];
        $payment->client_contact_id = $client_contact_id;
        $payment->save();

        return $payment;
    }

    /**
     * Creates a new String Payment Intent
     *
     * @param  array $data The data array to be passed to Stripe
     * @return PaymentIntent       The Stripe payment intent object
     */
    public function createPaymentIntent($data) :?\Stripe\PaymentIntent
    {
        $this->init();

        return PaymentIntent::create($data);
    }

    /**
     * Returns a setup intent that allows the user
     * to enter card details without initiating a transaction.
     *
     * @return \Stripe\SetupIntent
     */
    public function getSetupIntent() :\Stripe\SetupIntent
    {
        $this->init();

        return SetupIntent::create();
    }


    /**
     * Returns the Stripe publishable key
     * @return NULL|string The stripe publishable key
     */
    public function getPublishableKey() :?string
    {
        return $this->company_gateway->getPublishableKey();
    }

    /**
     * Finds or creates a Stripe Customer object
     *
     * @return NULL|\Stripe\Customer A Stripe customer object
     */
    public function findOrCreateCustomer() :?\Stripe\Customer
    {
        $customer = null;

        $this->init();

        $client_gateway_token = ClientGatewayToken::whereClientId($this->client->id)->whereCompanyGatewayId($this->company_gateway->id)->first();

        if ($client_gateway_token && $client_gateway_token->gateway_customer_reference) {
            $customer = \Stripe\Customer::retrieve($client_gateway_token->gateway_customer_reference);
        } else {
            $data['name'] = $this->client->present()->name();
            $data['phone'] = $this->client->present()->phone();

            if (filter_var($this->client->present()->email(), FILTER_VALIDATE_EMAIL)) {
                $data['email'] = $this->client->present()->email();
            }

            $customer = \Stripe\Customer::create($data);
        }

        if (!$customer) {
            throw new \Exception('Unable to create gateway customer');
        }

        return $customer;
    }

    public function refund(Payment $payment, $amount)
    {
        $this->gateway();

        $response = $this->gateway
            ->refund(['transactionReference'=>$payment->transaction_reference, 'amount' => $amount, 'currency' => $payment->client->getCurrencyCode()])
            ->send();

        if ($response->isSuccessful()) {
            SystemLogger::dispatch(
                [
                    'server_response' => $response->getMessage(),
                    'data' => request()->all(),
                ],
                SystemLog::CATEGORY_GATEWAY_RESPONSE,
                SystemLog::EVENT_GATEWAY_SUCCESS,
                SystemLog::TYPE_PAYPAL,
                $this->client
            );

            return true;
        }

        SystemLogger::dispatch(
            [
                'server_response' => $response->getMessage(),
                'data' => request()->all(),
            ],
            SystemLog::CATEGORY_GATEWAY_RESPONSE,
            SystemLog::EVENT_GATEWAY_FAILURE,
            SystemLog::TYPE_PAYPAL,
            $this->client
        );

        return false;
    }

    /************************************** Omnipay API methods **********************************************************/
}
