<?php

namespace App\Services\Payment\Methods;

use Amazon\Pay\API\Client;
use App\Exceptions\PaymentProcessingException;
use App\Models\Payment;
use App\Models\PaymentMethod;
use App\Services\Payment\Payable;
use App\Services\Payment\Traits\PaymentServiceHelper;
use Illuminate\Http\Request;

class AmazonService extends AbstractPayment
{
    use PaymentServiceHelper;

    protected $client;

    protected $storeId;

    protected $publicKey;

    protected $merchantId;

    const AMAZON_STATE_OPEN = 'Open';

    const AMAZON_STATE_COMPLETED = 'Completed';

    const AMAZON_STATE_CANCELED = 'Canceled';

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

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

        $payment = $this->createPayment($payable);

        return [
            'payment' => $payment,
            'response' => [
                ...$transaction,
                'gateway' => $payable->getMethod()->slug,
                'pid' => $payable->getPid(),
            ],
        ];
    }

    /**
     * Handle a successful payment request.
     *
     * Sets up the payment method for Amazon and retrieves the payment using the provided
     * 'amazonCheckoutSessionId'. Completes the Amazon checkout session and verifies the
     * payment state. If the payment is not completed, an exception is thrown.
     *
     * @param  Request  $request  The incoming HTTP request containing payment details.
     * @return string The payment identifier of the successfully processed payment.
     *
     * @throws PaymentProcessingException If the payment is not completed.
     */
    public function handleSuccess(Request $request)
    {
        $this->setup($this->getPaymentMethod('amazon'));

        $sessionId = $request->input('amazonCheckoutSessionId');

        $payment = $this->getPaymentBySessionId($sessionId);
        $result = $this->completeAmazonCheckoutSession($payment);

        if ($result['statusDetails']['state'] !== self::AMAZON_STATE_COMPLETED) {
            throw new PaymentProcessingException('Payment not completed');
        }

        return $payment->payment_identifier;
    }

    /**
     * Handle a failed payment request.
     *
     * Validates the presence of 'amazonCheckoutSessionId' and 'pid' in the request.
     * Retrieves the payment using the provided 'pid'. If the payment is not found,
     * an exception is thrown. Returns the 'pid' if the payment retrieval is successful.
     *
     * @param  Request  $request  The incoming HTTP request containing payment details.
     * @return string The payment identifier ('pid').
     *
     * @throws PaymentProcessingException If required parameters are missing or payment is not found.
     */
    public function handleFailed(Request $request)
    {
        if (! $request->has(['amazonCheckoutSessionId', 'pid'])) {
            throw new PaymentProcessingException('Invalid request');
        }

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

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

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

        return $pid;
    }

    /**
     * Handles incoming webhooks from Amazon Pay.
     *
     * This method processes the webhook payload to verify the charge permission,
     * retrieve the associated payment, and complete the Amazon checkout session.
     * It returns the payment status based on the session state.
     *
     * @param  Request  $request  The incoming HTTP request containing the webhook data.
     * @return array An array with the payment status and details if successful.
     *
     * @throws PaymentProcessingException If the payment state is invalid.
     */
    public function handleWebhook(Request $request)
    {
        $this->setup($this->getPaymentMethod('amazon'));

        $requestBody = json_decode($request->getContent(), true);
        $message = json_decode($requestBody['Message']);
        $chargePermissionId = $message?->ChargePermissionId;

        $chargePermission = $this->getChargePermission($chargePermissionId);
        $pid = $chargePermission['merchantMetadata']['merchantReferenceId'];

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

        $result = $this->completeAmazonCheckoutSession($payment);

        return match ($result['statusDetails']['state']) {
            self::AMAZON_STATE_COMPLETED => [
                'status' => self::SUCCESS,
                'payment' => $payment,
            ],
            self::AMAZON_STATE_CANCELED => [
                'status' => self::CANCELLED,
                'payment' => $payment,
            ],
            default => throw new PaymentProcessingException('Invalid payment state', code: 1),
        };
    }

    /**
     * Create a checkout session for Amazon Pay.
     *
     * This method constructs the payload required for initiating an Amazon
     * Pay checkout session, including details for web checkout, payment
     * intent, merchant metadata, and store information. It generates a
     * signature for the payload and returns a link to the checkout page.
     *
     * @param  Payable  $payable  The payable object containing details
     *                            about the transaction, including the
     *                            amount and currency.
     * @return array An array containing the checkout link and
     *               associated parameters like payload, signature,
     *               amount, currency, public key, and merchant ID.
     */
    public function createCheckout(Payable $payable)
    {
        $payableAmount = $payable->getConvertedAmount();
        $payload = [
            'webCheckoutDetails' => [
                'checkoutReviewReturnUrl' => $this->getReturnUrl($payable->getMethod(), $payable->getPid(), url: route('payment.amazon.review', ['pid' => $payable->getPid()])),
                'checkoutResultReturnUrl' => $this->getReturnUrl($payable->getMethod(), $payable->getPid(), 'success'),
                'checkoutCancelUrl' => $this->getReturnUrl($payable->getMethod(), $payable->getPid(), 'cancel'),
            ],
            'paymentDetails' => [
                'paymentIntent' => 'AuthorizeWithCapture',
                'canHandlePendingAuthorization' => false,
                'softDescriptor' => 'Descriptor',
                'chargeAmount' => [
                    'amount' => $payableAmount,
                    'currencyCode' => $payable->getCurrency(),
                ],
            ],
            'merchantMetadata' => [
                'merchantReferenceId' => $payable->getPid(),
                'merchantStoreName' => config('app.name'),
                'noteToBuyer' => 'Add Fund to your account',
            ],
            'storeId' => $this->storeId,
        ];

        $jsonPayload = json_encode($payload, JSON_UNESCAPED_SLASHES);

        $signature = $this->client->generateButtonSignature($jsonPayload);

        return [
            'params' => [
                'payload' => $jsonPayload,
                'signature' => $signature,
                'amount' => $payableAmount,
                'currency' => $payable->getCurrency(),
                'publicKey' => $this->publicKey,
                'merchantId' => $this->merchantId,
            ],
        ];
    }

    /**
     * Configures the Amazon Pay client and initializes necessary parameters.
     *
     * @param  PaymentMethod  $paymentMethod  The payment method instance containing configuration metadata.
     *
     * @throws PaymentProcessingException If required configuration values are missing or invalid.
     */
    private function setup(PaymentMethod $paymentMethod)
    {
        $config = $paymentMethod->meta;

        $amazonConfig = [
            'public_key_id' => $config['public_key'],
            'private_key' => $config['private_key'],
            'region' => $config['region'],
            'sandbox' => $paymentMethod->is_test,
            'algorithm' => 'AMZN-PAY-RSASSA-PSS-V2',
        ];

        $this->storeId = $config['store_id'];
        $this->publicKey = $config['public_key'];
        $this->merchantId = $config['merchant_id'];

        $this->client = new Client($amazonConfig);
    }

    /**
     * Handles the Amazon Pay redirect flow
     *
     * @return string
     *
     * @throws PaymentProcessingException
     */
    public function review()
    {
        $this->setup($this->getPaymentMethod('amazon'));

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

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

        $payment->update([
            'gateway_id' => $sessionId,
        ]);

        $payload = [
            'paymentDetails' => [
                'paymentIntent' => 'AuthorizeWithCapture',
                'canHandlePendingAuthorization' => false,
                'softDescriptor' => 'Descriptor',
                'chargeAmount' => [
                    'amount' => $this->getPayableAmount($payment),
                    'currencyCode' => $payment->currency,
                ],
            ],
        ];

        $result = $this->client->updateCheckoutSession($sessionId, $payload);
        if ($result['status'] !== 200) {
            throw new PaymentProcessingException('Failed to update checkout session');
        }

        $response = json_decode($result['response'], true);

        return $response['webCheckoutDetails']['amazonPayRedirectUrl'];
    }

    /**
     * Complete Amazon checkout session.
     *
     * @return array
     *
     * @throws PaymentProcessingException
     */
    private function completeAmazonCheckoutSession(Payment $payment)
    {
        $payload = [
            'chargeAmount' => [
                'amount' => $this->getPayableAmount($payment),
                'currencyCode' => $payment->currency,
            ],
        ];

        $result = $this->client->completeCheckoutSession($payment->gateway_id, $payload);

        if ($result['status'] !== 200) {
            throw new PaymentProcessingException('Failed to complete checkout session');
        }

        return json_decode($result['response'], true);
    }

    /**
     * Fetch a charge permission by ID.
     *
     * @param  string  $chargePermissionId
     * @return array
     *
     * @throws PaymentProcessingException
     */
    private function getChargePermission($chargePermissionId)
    {
        $result = $this->client->getChargePermission($chargePermissionId);

        if ($result['status'] !== 200) {
            throw new PaymentProcessingException('Charge permission not found');
        }

        return json_decode($result['response'], true);
    }

    /**
     * Get the payable amount in the correct format for Amazon Pay.
     *
     * @return string
     */
    private function getPayableAmount(Payment $payment)
    {
        return (string) number_format($payment->convertedAmount, 2, '.', '');
    }
}
