<?php

namespace BuyMeCoffee\Builder\Methods\PayPal;

use BuyMeCoffee\Helpers\ArrayHelper;
use BuyMeCoffee\Helpers\PaymentHelper;
use BuyMeCoffee\Models\Supporters;
use BuyMeCoffee\Models\Transactions;
use BuyMeCoffee\Builder\Methods\BaseMethods;
use BuyMeCoffee\Classes\Vite;

if (!defined('ABSPATH')) exit; // Exit if accessed directly

class PayPal extends BaseMethods
{
    public function __construct()
    {
        parent::__construct(
            'PayPal',
            'paypal',
            'PayPal is the faster, safer way to send money, make an online payment, receive money or set up a merchant account.',
            Vite::staticPath() . 'images/PayPal.svg'
        );
        add_action('buymecoffee_make_payment_paypal', array($this, 'makePayment'), 10, 3);
        add_action('buymecoffee_paypal_action_web_accept', array($this, 'updateStatus'), 10, 2);
        add_action("buymecoffee_ipn_endpoint_paypal", array($this, 'verifyIpn'), 10, 2);
        add_action('buymecoffee_get_payment_settings_paypal', array($this, 'getPaymentSettings'));
        add_filter('buymecoffee_before_save_paypal', array($this, 'validateProSettings'), 10, 1);
        add_filter('buymecoffee_process_refund_paypal', array($this, 'processRefund'), 10, 2);
    }

    public function validateProSettings($settings)
    {
        // Only validate if payment type is 'pro'
        if (!isset($settings['payment_type']) || $settings['payment_type'] !== 'pro') {
            return $settings;
        }

        // Get the payment mode to determine which keys to validate
        $mode = isset($settings['payment_mode']) ? $settings['payment_mode'] : 'test';

        if ($mode === 'test') {
            $clientId = isset($settings['test_public_key']) ? trim($settings['test_public_key']) : '';
            $clientSecret = isset($settings['test_secret_key']) ? trim($settings['test_secret_key']) : '';
        } else {
            $clientId = isset($settings['live_public_key']) ? trim($settings['live_public_key']) : '';
            $clientSecret = isset($settings['live_secret_key']) ? trim($settings['live_secret_key']) : '';
        }

        // Check if keys are provided
        if (empty($clientId) || empty($clientSecret)) {
            wp_send_json_error(array(
                'message' => __('PayPal Pro requires both Client ID and Secret Key. Please provide valid credentials.', 'buy-me-coffee')
            ), 400);
        }

        // Validate credentials by attempting to get an access token
        try {
            // Temporarily set the settings for API validation
            $tempSettings = new PayPalSettings();

            // Create a mock settings object for validation
            $validationSettings = array(
                'payment_mode' => $mode,
                'test_public_key' => isset($settings['test_public_key']) ? $settings['test_public_key'] : '',
                'test_secret_key' => isset($settings['test_secret_key']) ? $settings['test_secret_key'] : '',
                'live_public_key' => isset($settings['live_public_key']) ? $settings['live_public_key'] : '',
                'live_secret_key' => isset($settings['live_secret_key']) ? $settings['live_secret_key'] : '',
            );

            // Test the credentials by getting access token
            $apiUrl = $mode === 'test'
                ? 'https://api-m.sandbox.paypal.com/v1/oauth2/token'
                : 'https://api-m.paypal.com/v1/oauth2/token';

            $auth = base64_encode($clientId . ':' . $clientSecret);

            $response = wp_remote_post($apiUrl, array(
                'headers' => array(
                    'Authorization' => 'Basic ' . $auth,
                    'Accept' => 'application/json',
                    'Accept-Language' => 'en_US',
                ),
                'body' => array(
                    'grant_type' => 'client_credentials',
                ),
                'timeout' => 30
            ));

            if (is_wp_error($response)) {
                wp_send_json_error(array(
                    /* translators: %s: Error message from PayPal API */
                    'message' => sprintf(__('PayPal API connection failed: %s', 'buy-me-coffee'), $response->get_error_message())
                ), 400);
            }

            $http_code = wp_remote_retrieve_response_code($response);
            $body = wp_remote_retrieve_body($response);

            if ($http_code !== 200) {
                $error = json_decode($body, true);
                $errorMessage = isset($error['error_description']) ? $error['error_description'] : (isset($error['error']) ? $error['error'] : 'Invalid credentials');

                wp_send_json_error(array(
                    /* translators: %s: Error message from PayPal API describing why credentials are invalid */
                    'message' => sprintf(__('PayPal credentials validation failed: %s. Please check your Client ID and Secret Key.', 'buy-me-coffee'), $errorMessage)
                ), 400);
            }

            // If we reach here, credentials are valid
        } catch (\Exception $e) {
            wp_send_json_error(array(
                /* translators: %s: Exception error message */
                'message' => sprintf(__('PayPal validation error: %s', 'buy-me-coffee'), $e->getMessage())
            ), 400);
        }

        return $settings;
    }

    public function sanitize($settings)
    {
        $currentSettings = $this->getSettings();
        $secretFields = ['test_secret_key', 'live_secret_key'];

        foreach ($settings as $key => $value) {
            if (strpos($key, 'has_') === 0) {
                unset($settings[$key]);
                continue;
            }

            if ($key === 'paypal_email') {
                $settings[$key] = sanitize_email($value);
            } else {
                $settings[$key] = sanitize_text_field($value);
            }

            if (in_array($key, $secretFields, true) && $settings[$key] === '' && !empty($currentSettings[$key])) {
                // Keep existing secrets when masked/empty values are submitted from UI.
                $settings[$key] = $currentSettings[$key];
            }
        }

        $settings['enable'] = ($settings['enable'] ?? 'no') === 'yes' ? 'yes' : 'no';
        $settings['payment_mode'] = ($settings['payment_mode'] ?? 'test') === 'live' ? 'live' : 'test';
        $settings['payment_type'] = ($settings['payment_type'] ?? 'standard') === 'pro' ? 'pro' : 'standard';
        $settings['disable_ipn_verification'] = ($settings['disable_ipn_verification'] ?? 'no') === 'yes' ? 'yes' : 'no';
        return $settings;
    }

    public function paymentConfirmation()
    {
        $this->verifyPublicRequestNonce();

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $chargeId = isset($_REQUEST['charge_id']) ? sanitize_text_field(wp_unslash($_REQUEST['charge_id'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $hash = isset($_REQUEST['hash']) ?  sanitize_text_field(wp_unslash($_REQUEST['hash'])) : '';

        if (!$chargeId || !$hash) {
            wp_send_json_error(array(
                'message' => __('Invalid request', 'buy-me-coffee'),
            ), 400);
        }

        $this->updatePayment($chargeId, $hash);
    }
    public function verifyIpn()
    {
        (new IPN())->ipnVerificationProcess();
    }

    public function getPaymentSettings()
    {
        $currentSettings = $this->getSettings();
        $settings = $currentSettings;
        $settings['test_secret_key'] = '';
        $settings['live_secret_key'] = '';
        $settings['has_test_secret_key'] = !empty($currentSettings['test_secret_key']);
        $settings['has_live_secret_key'] = !empty($currentSettings['live_secret_key']);

        wp_send_json_success(array(
            'settings' => $settings,
            'webhook_url' => site_url() . '?buymecoffee_ipn_listener=1&method=paypal'
        ), 200);
    }

    public function getSettings($key = null)
    {
        return PayPalSettings::get($key);
    }

    public function maybeShowModal($transactions, $paypalSettings)
    {
        $paymentIntent = $this->modalPaymentIntent($transactions);
        $responseData = [
            'nextAction' => 'paypal',
            'hash' => $transactions->entry_hash,
            'actionName' => 'custom',
            'buttonState' => 'hide',
            'purchase_units' => $paymentIntent,
            'confirmation_url' => $this->successUrl($transactions),
            'message_to_show' => __('Payment Modal is opening, Please complete the payment', 'buy-me-coffee'),
        ];

        wp_send_json_success($responseData, 200);
    }

    public function modalPaymentIntent($transactions)
    {
        $total = $transactions->payment_total ? $transactions->payment_total : 5;
        $total = $total / 100;
        $currencyCode = $transactions->currency;
        $intent = [
            'reference_id' => $transactions->entry_hash,
            'amount' => [
                'value' => $total,
                'breakdown' => [
                    'item_total' => [
                        'currency_code' => $currencyCode,
                        'value' => $total,
                    ]
                ]
            ],
            'items' => array([
                'name' => 'Buy coffee for you',
                'unit_amount' => [
                    'currency_code' => $currencyCode,
                    'value' => $total,
                ],
                'quantity' => '1',
            ])
        ];

        return apply_filters('buymecoffee_paypal_modal_payment_intent', $intent, $transactions);
    }

    public function makePayment($transactionId, $entryId, $form_data)
    {
        $paypalSettings = $this->getSettings();
        $transactionModel = new Transactions();
        $transaction = $transactionModel->find($transactionId);

        if ($paypalSettings['payment_type'] === 'pro') {
            $this->maybeShowModal($transaction, $paypalSettings);
        }

        $paypal_redirect = 'https://www.paypal.com/cgi-bin/webscr';

        if ($paypalSettings['payment_mode'] === 'test') {
            $paypal_redirect = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
        }

        $supportersModel = new Supporters();
        $supporter = $supportersModel->find($entryId);
        $listener_url = apply_filters('buymecoffee_paypal_ipn_url', site_url('?buymecoffee_ipn_listener=1&method=paypal'), $supporter);
        $currency = strtoupper(sanitize_text_field($supporter->currency ?? 'USD'));

        if (!$this->isSupportedCurrency($currency)) {
            wp_send_json_error(array(
                'message' => sprintf(
                    /* translators: %s: three-letter currency code */
                    __('PayPal does not support %s for checkout. Please choose a PayPal-supported currency such as USD, EUR, or GBP.', 'buy-me-coffee'),
                    $currency
                ),
            ), 400);
        }

        $amount = $this->formatPayPalAmount((int) $transaction->payment_total, $currency);
        if ((float) $amount <= 0) {
            wp_send_json_error(array(
                'message' => __('PayPal payment amount must be greater than zero.', 'buy-me-coffee'),
            ), 400);
        }

        $paypal_args = array(
            'cmd' => '_donations',
            'business' => $paypalSettings['paypal_email'],
            'item_name' => $this->getPayPalItemName($supporter),
            'amount' => $amount,
            'no_shipping' => '1',
            'no_note' => '1',
            'currency_code' => $currency,
            'charset' => 'UTF-8',
            'custom' => $transactionId,
            'return' => $this->successUrl($supporter),
            'notify_url' => $listener_url,
            'cancel_return' => $this->failedUrl($supporter),
        );

        $paypal_args = apply_filters('buymecoffee_paypal_payment_args', $paypal_args);

        $supportersModel->updateData($entryId, array(
            'payment_mode' => $paypalSettings['payment_mode']
        ));

        if ($transactionId) {
            $transactionModel->updateData($transactionId, array(
                'payment_mode' => $paypalSettings['payment_mode']
            ));
        }

        $paypal_redirect = add_query_arg($paypal_args, $paypal_redirect);

        wp_send_json_success(array(
            'message' => __('You are redirected for payment.', 'buy-me-coffee'),
            'id' => $entryId,
            'redirectTo' => $paypal_redirect,
            'messageToShow' => __('Your are redirecting to paypal now', 'buy-me-coffee')
        ), 200);
        exit;
    }

    private function formatPayPalAmount($amountInCents, $currency)
    {
        $amount = max(0, (int) $amountInCents) / 100;
        $zeroDecimalCurrencies = ['HUF', 'JPY', 'TWD'];

        if (in_array($currency, $zeroDecimalCurrencies, true)) {
            return (string) (int) round($amount, 0);
        }

        return number_format($amount, 2, '.', '');
    }

    private function getPayPalItemName($supporter)
    {
        $name = !empty($supporter->supporters_name)
            ? sprintf(
                /* translators: %s: supporter name */
                __('Coffee support from %s', 'buy-me-coffee'),
                $supporter->supporters_name
            )
            : __('Coffee support', 'buy-me-coffee');

        return mb_substr(wp_strip_all_tags($name), 0, 127);
    }

    private function isSupportedCurrency($currency)
    {
        $supportedCurrencies = [
            'AUD',
            'BRL',
            'CAD',
            'CNY',
            'CZK',
            'DKK',
            'EUR',
            'HKD',
            'HUF',
            'ILS',
            'JPY',
            'MYR',
            'MXN',
            'TWD',
            'NZD',
            'NOK',
            'PHP',
            'PLN',
            'GBP',
            'SGD',
            'SEK',
            'CHF',
            'THB',
            'USD',
        ];

        return in_array(strtoupper($currency), $supportedCurrencies, true);
    }

    public function successUrl($supporter)
    {
        return add_query_arg(array(
            'share_coffee' => '',
            'buymecoffee_success' => 1,
            'hash' => $supporter->entry_hash,
            'payment_method' => 'paypal'
        ), home_url());
    }

    public function failedUrl($supporter)
    {
        return add_query_arg(array(
            'buymecoffee_failed' => 1,
            'share_coffee' => '',
            'buymecoffee_submission' => $supporter->entry_hash,
            'payment_method' => 'paypal'
        ), home_url());
    }

    public function getTransactionUrl($url, $transaction)
    {
        if ($transaction->payment_mode === 'test') {
            return 'https://www.sandbox.paypal.com/activity/payment/' . $transaction->charge_id;
        }
        return 'https://www.paypal.com/activity/payment/' . $transaction->charge_id;
    }

    public function updateStatus($data, $payment_id)
    {
        $txnType = sanitize_text_field($data['txn_type'] ?? '');
        $paymentStatusRaw = sanitize_text_field($data['payment_status'] ?? '');
        if ($txnType != 'web_accept' && $txnType != 'cart' && $paymentStatusRaw != 'Refunded') {
            return;
        }

        if (empty($payment_id)) {
            return;
        }

        $transaction = (new Transactions())->find($payment_id);

        if (defined('BUYMECOFFEE_PAYPAL_IPN_DEBUG')) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging when BUYMECOFFEE_PAYPAL_IPN_DEBUG is defined
            error_log('IPN For Transaction: ' . wp_json_encode($transaction));
        }

        if (!$transaction) {
            return;
        }

        if ($transaction->payment_method != 'paypal') {
            return;
        }

        $configuredPayee = strtolower(trim($this->getSettings('paypal_email')));
        $receiverEmail = strtolower(trim($data['receiver_email'] ?? ($data['business'] ?? '')));
        if ($configuredPayee && $receiverEmail && $receiverEmail !== $configuredPayee) {
            $this->changeStatus('failed', $transaction);
            return;
        }

        $currency_code = strtolower(sanitize_text_field($data['mc_currency'] ?? ''));

        if ($currency_code != strtolower($transaction->currency)) {
            $this->changeStatus('failed', $transaction);
            return;
        }

        $payment_status = strtolower($paymentStatusRaw);

        $paypal_amount = isset($data['mc_gross']) ? (float)$data['mc_gross'] : 0;
        $expectedAmount = (float)number_format((float)($transaction->payment_total / 100), 2, '.', '');
        $receivedAmount = (float)number_format((float)$paypal_amount, 2, '.', '');
        $isMismatchAmount = abs($expectedAmount - $receivedAmount) > 0.01;

        if ($isMismatchAmount) {
            $this->changeStatus('failed', $transaction);
            return;
        }

        if ('completed' == $payment_status || $transaction->payment_mode == 'test') {
            $this->changeStatus('paid', $transaction, $data);
            return;
        }

        if ('pending' == $payment_status && isset($data['pending_reason'])) {
            $this->changeStatus('processing', $transaction, $data);
        }
    }

    public function changeStatus($status, $transaction = null,  $data = array())
    {
        $supportersModel = new Supporters();
        $transactionModel = new Transactions();

        $updateData = array(
            'payment_status' => $status,
            'updated_at' => current_time('mysql')
        );

        $supportersModel->updateData($transaction->entry_id, $updateData);

        if (!empty($data) && isset($data['txn_id'])) {
            $updateData = [
                'status' => $status,
                'updated_at' => current_time('mysql'),
                'payment_note' => wp_json_encode($data),
                'charge_id' => sanitize_text_field($data['txn_id'])
            ];
        }

        $transactionModel->updateData($transaction->id, $updateData);

    }

    public function maybeLoadModalScript()
    {
        $settings = $this->getSettings();
        if ($settings['payment_type'] == 'standard') {
            return;
        };

        $mode = $settings['payment_mode'];
        $clientId = $mode === 'test' ? $settings['test_public_key'] : $settings['live_public_key'];
        $currency = PaymentHelper::getCurrency() ?: 'USD';

        wp_enqueue_script(
            'wpm-buymecoffee-checkout-sdk-' . $this->method,
            add_query_arg(
                [
                    'client-id' => $clientId,
                    'currency'  => strtoupper($currency),
                    'intent'    => 'capture',
                ],
                'https://www.paypal.com/sdk/js'
            ),
            [],
            // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- PayPal SDK URL is generated by PayPal query params and must not be plugin-versioned.
            null,
            false
        );

        Vite::enqueueScript('wpm-buymecoffee-checkout-handler-' . $this->method, 'js/PaymentMethods/paypal-checkout.js', ['wpm-buymecoffee-checkout-sdk-paypal', 'jquery'], '1.0.1', false);

    }

    public function render($template)
    {
        $this->maybeLoadModalScript();
        $id = $this->uniqueId('paypal_card');

        ?>
        <label class="wpm_paypal_card_label" for="<?php echo esc_attr($id); ?>">
            <img width="60px" src="<?php echo esc_url(Vite::staticPath() . 'images/PayPal.svg'); ?>" alt="">
            <input
                    style="outline: none;"
                    type="radio" name="wpm_payment_method" class="wpm_paypal_card" id="<?php echo esc_attr($id); ?>"
                    value="paypal"/>
        </label>
        <?php
    }


    public function updatePayment($chargeId, $hash)
    {
        $transaction = (new Transactions())->find($hash, 'entry_hash');
        if (!$transaction || $transaction->payment_method !== 'paypal') {
            wp_send_json_error(array(
                'message' => __('Transaction not found', 'buy-me-coffee'),
            ), 404);
        }

        $api = new API();
        try {
            $payment_intent = $api->verifyTransaction($chargeId);
        } catch (\Exception $e) {
            wp_send_json_error(array(
                'message' => esc_html($e->getMessage()),
            ), 400);
        }

        $orderStatus = strtoupper(sanitize_text_field(ArrayHelper::get($payment_intent, 'status', '')));
        if ($orderStatus === 'APPROVED') {
            try {
                // Capture server-side to avoid browser token/runtime issues after buyer approval.
                $payment_intent = $api->captureTransaction($chargeId);
            } catch (\Exception $e) {
                wp_send_json_error(array(
                    'message' => esc_html($e->getMessage()),
                ), 400);
            }
            $orderStatus = strtoupper(sanitize_text_field(ArrayHelper::get($payment_intent, 'status', '')));
        }

        if ($orderStatus !== 'COMPLETED') {
            $statusMessage = $orderStatus ? $orderStatus : __('UNKNOWN', 'buy-me-coffee');
            wp_send_json_error(array(
                /* translators: %s: PayPal order status (e.g. PENDING, VOIDED) */
                'message' => sprintf(__('PayPal order is not completed yet. Current status: %s', 'buy-me-coffee'), $statusMessage),
            ), 400);
        }

        $purchaseUnit = ArrayHelper::get($payment_intent, 'purchase_units.0');
        $referenceId = sanitize_text_field(ArrayHelper::get($purchaseUnit, 'reference_id', ''));
        if ($referenceId !== $hash) {
            wp_send_json_error(array(
                'message' => __('Payment confirmation reference mismatch', 'buy-me-coffee'),
            ), 400);
        }

        $vendorChargeId = ArrayHelper::get($purchaseUnit, 'payments.captures.0.id', '');
        if (!$vendorChargeId) {
            $vendorChargeId = sanitize_text_field($chargeId);
        }
        $amount = (int) round(100 * floatval(ArrayHelper::get($purchaseUnit, 'amount.value', 0)), 0);
        $currencyCode = strtoupper(sanitize_text_field(ArrayHelper::get($purchaseUnit, 'amount.currency_code', '')));
        $mode = $this->getSettings('payment_mode');

        if (!empty($currencyCode) && strtoupper($transaction->currency) !== $currencyCode) {
            wp_send_json_error(array(
                'message' => __('Payment currency mismatch', 'buy-me-coffee'),
            ), 400);
        }

        if (!empty($transaction->payment_total) && abs((int)$transaction->payment_total - $amount) > 1) {
            wp_send_json_error(array(
                'message' => __('Payment amount mismatch', 'buy-me-coffee'),
            ), 400);
        }

        $updateData = array(
            'charge_id' => sanitize_text_field($vendorChargeId),
            'payment_mode' => sanitize_text_field($mode),
            'payment_total' => $amount,
            'payment_note' => wp_json_encode($payment_intent),
            'status' => 'paid',
            'updated_at' => current_time('mysql'),
        );

        (new Transactions())->updateData($transaction->id, $updateData);

        (new Supporters())->updateData($transaction->entry_id, [
            'payment_total' => $amount,
            'payment_status' => 'paid',
            'payment_mode' => sanitize_text_field($mode),
            'updated_at' => current_time('mysql')
        ]);

        do_action('buymecoffee_payment_status_updated', $transaction->id, 'paid');

        wp_send_json_success(array(
            'message' => __('Payment updated successfully', 'buy-me-coffee'),
            'data' => $transaction->id
        ), 200);
    }

    /**
     * Handle a refund request for a PayPal transaction.
     * Hooked via filter: buymecoffee_process_refund_paypal
     *
     * PayPal Standard only records txn_id (not a capture ID), so REST refunds
     * are only supported for PayPal Pro.
     *
     * @param null      $result     Incoming filter value (null = unhandled).
     * @param object    $transaction Transaction object.
     * @return true|\WP_Error  true on success, WP_Error on failure.
     */
    public function processRefund($result, $transaction)
    {
        $settings = $this->getSettings();

        if (($settings['payment_type'] ?? 'standard') !== 'pro') {
            return new \WP_Error(
                'paypal_standard_no_api_refund',
                __('PayPal Standard refunds must be processed directly from your PayPal dashboard.', 'buy-me-coffee')
            );
        }

        try {
            $response = API::makeRequest(
                'payments/captures/' . $transaction->charge_id . '/refund',
                'v2',
                'POST',
                []
            );

            if (is_wp_error($response)) {
                return $response;
            }

            $status = strtoupper(sanitize_text_field(ArrayHelper::get($response, 'status', '')));
            if (!$status && (int) ArrayHelper::get($response, 'code', 0) === 204) {
                $status = 'COMPLETED';
            }

            if (!$status) {
                return new \WP_Error('paypal_refund_invalid', __('PayPal refund response is invalid.', 'buy-me-coffee'));
            }

            if (in_array($status, ['DECLINED', 'FAILED', 'CANCELLED'], true)) {
                return new \WP_Error('paypal_refund_failed', __('PayPal refund failed. Please check your PayPal dashboard.', 'buy-me-coffee'));
            }

            return [
                'status' => strtolower($status),
                'refund_id' => sanitize_text_field(ArrayHelper::get($response, 'id', '')),
            ];
        } catch (\Exception $e) {
            return new \WP_Error('paypal_refund_exception', $e->getMessage());
        }
    }

    public function isEnabled()
    {
        // TODO: Implement isEnabled() method.
        $settings = $this->getSettings();
        return $settings['enable'] === 'yes';
    }
}
