<?php

namespace App\Services\Payment\Methods;

use App\Exceptions\PaymentProcessingException;
use App\Models\PaymentMethod;
use App\Services\Payment\Payable;
use App\Services\Payment\Traits\PaymentServiceHelper;
use Illuminate\Http\Request;
use Omnipay\Omnipay;

class PayPalService extends AbstractPayment
{
    use PaymentServiceHelper;

    private $gateway;

    protected $clientId;

    protected $secret;

    protected $baseUrl;

    const PAYPAL_STATE_CREATED = 'created';

    const PAYPAL_STATE_APPROVED = 'approved';

    const PAYPAL_STATE_FAILED = 'failed';

    const PAYPAL_STATE_CANCELED = 'canceled';

    const PAYPAL_STATE_EXPIRED = 'expired';

    public function pay(Payable $payable, $rest = [])
    {
        $this->setup($payable->getMethod());

        $transaction = $this->createOrder($payable);

        $payment = $this->createPayment($payable, $transaction['gateway_id']);

        return [
            'payment' => $payment,
            'response' => [
                'payment_url' => $transaction['link'],
            ],
        ];
    }

    public function handleSuccess(Request $request)
    {
        if (! $this->validatePaymentRequest($request)) {
            throw new PaymentProcessingException(__('Invalid payment request'));
        }

        if (! $this->gateway) {
            $this->setup($this->getPaymentMethod('paypal'));
        }

        $response = $this->completePaypalPurchase($request);

        if (! $response->isSuccessful()) {
            throw new PaymentProcessingException(__('Payment is not successful'));
        }

        $data = $response->getData();
        $payment = $this->getPaymentBySessionId($data['id']);

        if ($data['state'] != self::PAYPAL_STATE_APPROVED) {
            throw new PaymentProcessingException(__('Payment not approved'));
        }

        return $payment->payment_identifier;
    }

    public function handleFailed(Request $request)
    {

        if (! $request->filled('pid')) {
            throw new PaymentProcessingException(__('Invalid request'));
        }

        $pid = $request->input('pid');

        $payment = $this->getPaymentByPid($pid);

        if (! $payment) {
            throw new PaymentProcessingException(__('Payment not found'));
        }

        return $payment->payment_identifier;
    }

    public function handleWebhook(Request $request)
    {
        $event = $request->all();

        if ($event['event_type'] != 'PAYMENTS.PAYMENT.CREATED') {
            throw new PaymentProcessingException('Invalid event type');
        }

        if (! $this->gateway) {
            $this->setup($this->getPaymentMethod('paypal'));
        }

        $payment = $this->getPaymentBySessionId($event['resource']['id']);

        $response = $this->completePaypalPurchase([
            'payer_id' => $event['resource']['payer']['payer_info']['payer_id'],
            'transactionReference' => $event['resource']['id'],
        ]);

        if (! $response->isSuccessful()) {
            throw new PaymentProcessingException($response->getMessage());
        }

        $data = $response->getData();

        return match ($data['state']) {
            self::PAYPAL_STATE_APPROVED => [
                'status' => 'success',
                'payment' => $payment,
            ],
            self::PAYPAL_STATE_FAILED => [
                'status' => 'failed',
                'payment' => $payment,
            ],
            self::PAYPAL_STATE_CANCELED => [
                'status' => 'cancelled',
                'payment' => $payment,
            ],
            default => throw new PaymentProcessingException(__('Invalid payment status'), code: 1),
        };
    }

    public function createOrder(Payable $payable)
    {
        $response = $this->gateway
            ->purchase([
                'amount' => $payable->getConvertedAmount(),
                'currency' => $payable->getCurrency(),
                'returnUrl' => $this->getReturnUrl($payable->getMethod(), $payable->getPid(), 'success'),
                'cancelUrl' => $this->getReturnUrl($payable->getMethod(), $payable->getPid(), 'cancel'),
            ])
            ->send();

        return [
            'gateway_id' => $response->getData()['id'],
            'link' => $response->getRedirectUrl(),
        ];
    }

    private function setup(PaymentMethod $paymentMethod)
    {
        $config = $paymentMethod->meta;

        $gateway = Omnipay::create('PayPal_Rest');
        $gateway->setClientId($config['client_id']);
        $gateway->setSecret($config['client_secret']);
        $gateway->setTestMode($paymentMethod->is_test);

        $this->gateway = $gateway;
    }

    private function validatePaymentRequest(Request $request): bool
    {
        return $request->filled(['paymentId', 'PayerID']);
    }

    private function completePaypalPurchase($data)
    {
        return $this->gateway
            ->completePurchase([
                'payer_id' => $data['payer_id'] ?? $data['PayerID'],
                'transactionReference' => $data['transactionReference'] ?? $data['paymentId'],
            ])
            ->send();
    }
}
