<?php

namespace Modules\PayPal\Payment;

use Exception;
use Illuminate\Support\Str;
use App\Enums\PaymentStatus;
use App\Http\Traits\PaymentTrait;
use App\Models\PaymentTransactions;
use Modules\PayPal\Enums\PaypalEvent;
use Modules\PayPal\Enums\PaypalCurrencies;

class PayPal
{
    use PaymentTrait;

    public static function getPayPalConfigs()
    {
        return config('paypal.configs');
    }

    public static function getPayPalPaymentUrl()
    {
        $paypal = self::getPayPalConfigs();

        return ($paypal['paypal_mode'] == '1') ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
    }

    public static function getAccessToken()
    {

        $paypal = self::getPayPalConfigs();
        $payment_url = self::getPayPalPaymentUrl();
        if (!empty($paypal)) {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $payment_url . '/v1/oauth2/token');
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
            curl_setopt($ch, CURLOPT_USERPWD, @$paypal['paypal_client_id'] . ':' . @$paypal['paypal_client_secret']);

            $headers = [];
            $headers[] = 'Content-Type: application/x-www-form-urlencoded';
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

            $accessToken = curl_exec($ch);
            if (curl_errno($ch)) {
                echo 'Error:' . curl_error($ch);
            }

            curl_close($ch);
            return json_decode($accessToken, true);
        }
    }

    public static function getIntent($obj, $request)
    {
        try {

            $payment = PaymentTransactions::updateOrCreate([
                'item_id' => $obj?->id,
                'type' => $request->type,
                'is_verified' => false,
            ], [
                'item_id' => $obj?->id,
                'transaction_id' => uniqid(),
                'amount' => $obj?->total,
                'payment_method' => config('paypal.name'),
                'payment_status' => PaymentStatus::PENDING,
                'type' => $request->type,
                'request_type' => $request->request_type,
                'redirect_url' => $request->redirect_url
            ]);

            $defaultCurrencyCode = getDefaultCurrencyCode();
            if (!in_array($defaultCurrencyCode, array_column(PaypalCurrencies::cases(), 'value'))) {
                throw new Exception($defaultCurrencyCode . ' currency code is not support for PayPal.', 400);
            }

            $token = self::getAccessToken();
            $payment_url = self::getPayPalPaymentUrl();
            if (isset($token['access_token'])) {
                $payload = [];
                $payload['intent'] = 'CAPTURE';
                $rawAmount = roundNumber($obj->total);
                $payload['purchase_units'] = [
                    [
                        'invoice_id' => $obj?->id,
                        'amount' => [
                            'currency_code' => $request->currency_code ?? $defaultCurrencyCode,
                            'value' => number_format(roundNumber($obj->total), 2, '.', ''),
                        ],
                        'description' => 'Details From ' . config('app.name'),
                    ],
                ];
                $payload['application_context'] = [
                    'brand_name' => config('app.name'),
                    'user_action' => 'PAY_NOW',
                    'payment_method' => [
                        'payer_selected' => 'PAYPAL',
                        'payee_preferred' => 'IMMEDIATE_PAYMENT_REQUIRED',
                    ],
                    'return_url' => route('paypal.status'),
                    'cancel_url' => route('paypal.status'),
                ];

                $ch = curl_init();
                curl_setopt($ch, CURLOPT_URL, $payment_url . '/v2/checkout/orders');
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($ch, CURLOPT_POST, 1);
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));

                $headers = [];
                $headers[] = 'Content-Type: application/json';
                $headers[] = 'Authorization: Bearer ' . $token['access_token'];
                $headers[] = 'Paypal-Request-Id:' . Str::uuid();
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
                $transaction = curl_exec($ch);
                if (curl_errno($ch)) {
                    throw new Exception('Error:' . curl_error($ch), 500);
                }

                curl_close($ch);
                $transaction = json_decode($transaction, true);

                if (isset($transaction['links']) && isset($transaction['id'])) {
                    self::updatePaymentTransactionId($payment, $transaction['id']);
                    return [
                        'item_id' => $obj?->id,
                        'url' => next($transaction['links'])['href'],
                        'transaction_id' => $transaction['id'],
                        'type' => $request->type,
                        'is_redirect' => true,
                    ];
                }
            }

            throw new Exception("Something went to wrong in paypal gateway", 500);

        } catch (Exception $e) {

            self::updatePaymentStatusByType($obj?->id, $request?->type, PaymentStatus::FAILED);
            throw new Exception($e->getMessage(), $e->getCode());
        }
    }

    public static function status($transaction_id)
    {
        try {

            $token = self::getAccessToken();
            $payment_url = self::getPayPalPaymentUrl();
            if (isset($token['access_token'])) {
                $ch = curl_init();
                curl_setopt($ch, CURLOPT_URL, $payment_url . "/v2/checkout/orders/{$transaction_id}/capture");
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($ch, CURLOPT_POST, 1);

                $headers = [];
                $headers[] = 'Content-Type: application/json';
                $headers[] = 'Authorization: Bearer ' . $token['access_token'];
                $headers[] = 'Paypal-Request-Id:' . Str::uuid();
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

                $payment = curl_exec($ch);
                if (curl_errno($ch)) {
                    echo 'Error:' . curl_error($ch);
                }
                curl_close($ch);
                $payment = json_decode($payment);
                $payment_status = PaymentStatus::PENDING;
                $paymentTransaction = self::getPaymentByTransactionId($transaction_id);
                if (isset($payment?->status)) {
                    $payment_status = $payment?->status;
                } elseif (!isset($payment?->status) && isset($payment?->details)) {
                    if (head($payment?->details)?->issue == PaypalEvent::ORDER_ALREADY_CAPTURED) {
                        return $paymentTransaction;
                    }
                } else {
                    $payment_status = PaymentStatus::FAILED;
                }

                return self::updatePaymentStatus($paymentTransaction, $payment_status);
            }

            throw new Exception('Provided transaction id is invalid!', 400);
        } catch (Exception $e) {

            self::updatePaymentStatusByTrans($transaction_id, PaymentStatus::FAILED);
            throw new Exception($e->getMessage(), $e->getCode());
        }
    }

    public static function getPaymentByTransactionId($transaction_id)
    {
        return PaymentTransactions::where('transaction_id', $transaction_id)->first();
    }

    public static function getPaymentByItemId($item_id)
    {
        return PaymentTransactions::where('item_id', $item_id)->first();
    }

    public static function webhook($request)
    {
        try {

            $config = self::getPayPalConfigs();
            $payment_url = self::getPayPalPaymentUrl();
            $token = self::getAccessToken();

            if (isset($token['access_token'])) {
                $payload = [
                    'auth_algo' => $request->header('PAYPAL-AUTH-ALGO', null),
                    'cert_url' => $request->header('PAYPAL-CERT-URL', null),
                    'transmission_id' => $request->header('PAYPAL-TRANSMISSION-ID', null),
                    'transmission_sig' => $request->header('PAYPAL-TRANSMISSION-SIG', null),
                    'transmission_time' => $request->header('PAYPAL-TRANSMISSION-TIME', null),
                    'webhook_id' => $config->webhook_id,
                    'webhook_event' => $request->all(),
                ];

                $ch = curl_init();
                curl_setopt($ch, CURLOPT_URL, $payment_url . '/v1/notifications/verify-webhook-signature');
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($ch, CURLOPT_POST, 1);
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));

                $headers = [];
                $headers[] = 'Content-Type: application/json';
                $headers[] = 'Authorization: Bearer ' . $token['access_token'];
                $headers[] = 'Paypal-Request-Id:' . Str::uuid();
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
                $event = curl_exec($ch);
                if (!isset($event['verification_status'])) {
                    throw new Exception($event['error']['name'], 500);
                }

                switch ($request->event_type) {
                    case PaypalEvent::COMPLETED:
                        $payment_status = PaymentStatus::COMPLETED;
                        break;

                    case PaypalEvent::PENDING:
                        $payment_status = PaymentStatus::PENDING;
                        break;

                    case PaypalEvent::REFUNDED:
                        $payment_status = PaymentStatus::REFUNDED;
                        break;

                    case PaypalEvent::DECLINED:
                    case PaypalEvent::CANCELLED:
                        $payment_status = PaymentStatus::CANCELLED;
                        break;

                    default:
                        $payment_status = PaymentStatus::FAILED;
                }

                $paymentTransaction = self::getPaymentByItemId($request->resource['invoice_id']);
                return self::updatePaymentStatus($paymentTransaction, $payment_status);
            }

            throw new Exception('Provided transactions id is invalid!', 400);

        } catch (Exception $e) {

            throw new Exception($e->getMessage(), $e->getCode());
        }
    }
}
