<?php

namespace FluentForm\App\Modules\Payments;

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

use FluentForm\App\Helpers\Helper;
use FluentForm\App\Models\Form;
use FluentForm\App\Models\Submission;
use FluentForm\App\Models\Subscription;
use FluentForm\App\Models\Transaction;
use FluentForm\App\Modules\Form\FormFieldsParser;
use FluentForm\App\Modules\Payments\PaymentMethods\BaseProcessor;
use FluentForm\Database\Migrations\Submissions;
use FluentForm\Framework\Helpers\ArrayHelper;
use FluentForm\App\Modules\Payments\Classes\PaymentManagement;
use FluentForm\App\Modules\Payments\Migrations\Migration;
use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\ConnectConfig;

class AjaxEndpoints
{
    public function handleEndpoint($route)
    {
        $validRoutes = [
            'enable_payment'               => 'enablePaymentModule',
            'update_global_settings'       => 'updateGlobalSettings',
            'get_payment_method_settings'  => 'getPaymentMethodSettings',
            'save_payment_method_settings' => 'savePaymentMethodSettings',
            'get_form_settings'            => 'getFormSettings',
            'save_form_settings'           => 'saveFormSettings',
            'update_transaction'           => 'updateTransaction',
            'get_stripe_connect_config'    => 'getStripeConnectConfig',
            'disconnect_stripe_connection' => 'disconnectStripeConnect',
            'get_pages'                    => 'getWpPages',
            'cancel_subscription'          => 'cancelSubscription'
        ];

        if (isset($validRoutes[$route])) {
            $this->{$validRoutes[$route]}();
        } else {
            do_action('fluentform/handle_payment_ajax_endpoint', $route);
        }

        die();
    }

    public function enablePaymentModule()
    {
        $this->upgradeDb();
        // Update settings
        $settings = PaymentHelper::updatePaymentSettings([
            'status' => 'yes'
        ]);
        // send response to reload the page

        wp_send_json_success([
            'message'  => __('Payment Module successfully enabled!', 'fluentform'),
            'settings' => $settings,
            'reload'   => 'yes'
        ]);
    }

    private function upgradeDB()
    {
        global $wpdb;
        $table = $wpdb->prefix . 'fluentform_transactions';
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder -- Checking table structure, %1s is for identifier
        $cols = $wpdb->get_col($wpdb->prepare("DESC %1s", $table), 0);

        if ($cols && in_array('subscription_id', $cols) && in_array('transaction_hash', $cols)) {
            // We are good
        } else {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder -- Migration, dropping table to recreate, %1s is for identifier
            $wpdb->query($wpdb->prepare("DROP TABLE IF EXISTS %1s", $table));
            Migration::migrate();
            // Migrate the database
            Submissions::migrate(true); // Add payment_total
        }
    }

    public function updateGlobalSettings()
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified in route registration, sanitized in updatePaymentSettings()
        $request = wpFluentForm()->request;
        $settings = wp_unslash($request->get('settings', []));
        
        $sanitizeMap = [
            'status'   => 'sanitize_text_field',
            'currency' => 'sanitize_text_field',
        ];
        $settings = fluentform_backend_sanitizer($settings, $sanitizeMap);

        // Update settings
        $settings = PaymentHelper::updatePaymentSettings($settings);

        // send response to reload the page
        wp_send_json_success([
            'message'  => __('Settings successfully updated!', 'fluentform'),
            'settings' => $settings,
            'reload'   => 'yes'
        ]);

    }

    public function getPaymentMethodSettings()
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in route registration
        $request = wpFluentForm()->request;
        $method = sanitize_text_field($request->get('method', ''));

        $paymentSettings = apply_filters_deprecated(
            'fluentform_payment_settings_' . $method,
            [
                []
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/payment_settings_' . $method,
            'Use fluentform/payment_settings_' . $method . ' instead of fluentform_payment_settings_' . $method
        );

        $settings = apply_filters('fluentform/payment_settings_' . $method, $paymentSettings);

        wp_send_json_success([
            'settings' => ($settings) ? $settings : false
        ]);
    }

    public function savePaymentMethodSettings()
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in route registration
        $request = wpFluentForm()->request;
        $method = sanitize_text_field($request->get('method', ''));
        $settings = wp_unslash($request->get('settings', []));
        
        $sanitizeMap = [
            'status' => 'sanitize_text_field',
        ];
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified in route registration, sanitized in validation filter
        $settings = fluentform_backend_sanitizer($settings, $sanitizeMap);


        $settingsValidation = apply_filters_deprecated(
            'fluentform_payment_method_settings_validation_' . $method,
            [
                [],
                $settings
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/payment_method_settings_validation_' . $method,
            'Use fluentform/payment_method_settings_validation_' . $method . ' instead of fluentform_payment_method_settings_validation_' . $method
        );

        $validationErrors = apply_filters('fluentform/payment_method_settings_validation_' . $method, $settingsValidation, $settings);

        if ($validationErrors) {
            wp_send_json_error([
                'message' => __('Failed to save settings', 'fluentform'),
                'errors'  => $validationErrors
            ], 423);
        }

        $settings = apply_filters_deprecated(
            'fluentform_payment_method_settings_save_' . $method,
            [
                $settings
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/payment_method_settings_save_' . $method,
            'Use fluentform/payment_method_settings_save_' . $method . ' instead of fluentform_payment_method_settings_save_' . $method
        );

        $settings = apply_filters('fluentform/payment_method_settings_save_' . $method, $settings);

        update_option('fluentform_payment_settings_' . $method, $settings, 'yes');

        wp_send_json_success([
            'message' => __('Settings successfully updated', 'fluentform')
        ]);
    }

    public function getFormSettings()
    {
        $request = wpFluentForm()->request;
        $formId = intval($request->get('form_id', 0));
        $settings = PaymentHelper::getFormSettings($formId, 'admin');
        $form = Form::find($formId);
        $addressFields = array_values(FormFieldsParser::getAddressFields($form));

        $paymentSettings = [
            'settings'        => $settings,
            'currencies'      => PaymentHelper::getCurrencies(),
            'payment_methods' => PaymentHelper::getFormPaymentMethods($formId),
            'addressFields'   => array_filter($addressFields)
        ];

        $paymentSettings = apply_filters('fluentform/form_payment_settings', $paymentSettings, $formId);

        wp_send_json_success($paymentSettings, 200);
    }

    public function saveFormSettings()
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in route registration
        $request = wpFluentForm()->request;
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified in route registration, sanitized in setFormMeta()
        $formId = intval($request->get('form_id', 0));
        $settings = wp_unslash($request->get('settings', []));
        
        $sanitizeMap = [
            'enabled'  => 'rest_sanitize_boolean',
            'currency' => 'sanitize_text_field',
        ];
        $settings = fluentform_backend_sanitizer($settings, $sanitizeMap);
        
        Helper::setFormMeta($formId, '_payment_settings', $settings);

        wp_send_json_success([
            'message' => __('Settings successfully saved', 'fluentform')
        ], 200);
    }

    public function updateTransaction()
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Nonce verified in route registration, data sanitized below
        $request = wpFluentForm()->request;
        $transactionData = $request->get('transaction', []);
        if (is_array($transactionData)) {
            $transactionData['id'] = intval(ArrayHelper::get($transactionData, 'id'));
            $transactionData['status'] = sanitize_text_field(ArrayHelper::get($transactionData, 'status'));
            $transactionData['payer_name'] = sanitize_text_field(ArrayHelper::get($transactionData, 'payer_name'));
            $transactionData['payer_email'] = sanitize_email(ArrayHelper::get($transactionData, 'payer_email'));
            $transactionData['charge_id'] = sanitize_text_field(ArrayHelper::get($transactionData, 'charge_id'));
            $transactionData['refund_amount'] = floatval(ArrayHelper::get($transactionData, 'refund_amount'));
            $transactionData['refund_note'] = sanitize_text_field(ArrayHelper::get($transactionData, 'refund_note'));
            $transactionData['should_run_actions'] = sanitize_text_field(ArrayHelper::get($transactionData, 'should_run_actions'));
            
            // Handle billing_address and shipping_address
            if (isset($transactionData['billing_address'])) {
                $transactionData['billing_address'] = is_array($transactionData['billing_address']) 
                    ? array_map('sanitize_text_field', $transactionData['billing_address'])
                    : sanitize_text_field($transactionData['billing_address']);
            }
            if (isset($transactionData['shipping_address'])) {
                $transactionData['shipping_address'] = is_array($transactionData['shipping_address']) 
                    ? array_map('sanitize_text_field', $transactionData['shipping_address'])
                    : sanitize_text_field($transactionData['shipping_address']);
            }
        }
        
        // Sanitize subscription_id separately
        $subscriptionId = intval($request->get('subscription_id', 0));
        
        $transactionId = $transactionData['id'];
        $oldTransaction = Transaction::find($transactionId);

        $changingStatus = $oldTransaction->status != $transactionData['status'];

        $updateData = ArrayHelper::only($transactionData, [
            'payer_name',
            'payer_email',
            'billing_address',
            'shipping_address',
            'charge_id',
            'status'
        ]);

        $updateData['updated_at'] = current_time('mysql');

        Transaction::where('id', $transactionId)->update($updateData);

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in route registration
        if ($subscriptionId) {
            $existingSubscription = Subscription::find($subscriptionId);

            $changedStatus = ArrayHelper::get($transactionData, 'status');

            $isStatusChanged = $existingSubscription->status != $changedStatus;

            if ($isStatusChanged) {
                Subscription::where('id', $subscriptionId)
                    ->update([
                        'status' => $changedStatus,
                        'updated_at' => current_time('mysql')
                    ]);
            }
        }

        $newStatus = $transactionData['status'];

        // No need abstract method, only need defined method, empty implementation
        $baseProcessor = new class extends BaseProcessor {
            public function handlePaymentAction($submissionId, $submissionData, $form, $methodSettings, $hasSubscriptions, $totalPayable) {
            }
        };

        if (
            ($changingStatus && ($newStatus == 'refunded' || $newStatus == 'partial-refunded')) ||
            ($newStatus == 'partial-refunded' && ArrayHelper::get($transactionData, 'refund_amount'))
        ) {
            $refundAmount = 0;
            $refundNote = 'Refunded by Admin';

            if ($newStatus == 'refunded') {
                // Handle refund here
                $refundAmount = $oldTransaction->payment_total;
            } else if ($newStatus == 'partially-refunded') {
                $refundAmount = ArrayHelper::get($transactionData, 'refund_amount') * 100;
                $refundNote = ArrayHelper::get($transactionData, 'refund_note');
            }

            if ($refundAmount) {
                $baseProcessor->setSubmissionId($oldTransaction->submission_id);

                $submission = $baseProcessor->getSubmission();
                $baseProcessor->refund($refundAmount, $oldTransaction, $submission, $oldTransaction->payment_method, 'refund_' . time(), $refundNote);
            }

        }

        if ($changingStatus) {

            if ($newStatus == 'paid' || $newStatus == 'pending' || $newStatus == 'processing') {
                // Delete All Refunds
                Transaction::bySubmission($oldTransaction->submission_id)->refunds()->delete();
            }

            $baseProcessor->setSubmissionId($oldTransaction->submission_id);
            $baseProcessor->changeSubmissionPaymentStatus($newStatus);
            $baseProcessor->changeTransactionStatus($transactionId, $newStatus);
            $baseProcessor->recalculatePaidTotal();
        }

        $shouldRunActions = ArrayHelper::get($transactionData, 'should_run_actions', 'no');

        if (
            $changingStatus &&
            $newStatus === 'paid' &&
            $shouldRunActions === 'yes'
        ) {
            do_action(
                'fluentform/run_actions_after_update_transaction_as_paid',
                $newStatus,
                $oldTransaction
            );
        }

        wp_send_json_success([
            'message' => __('Successfully updated data', 'fluentform')
        ], 200);
    }

    public function getStripeConnectConfig()
    {
        wp_send_json_success(ConnectConfig::getConnectConfig());
    }

    public function disconnectStripeConnect()
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in route registration
        $request = wpFluentForm()->request;
        $attributes = $request->all();
        
        $sanitizeMap = [
            'mode' => 'sanitize_text_field',
        ];
        $attributes = fluentform_backend_sanitizer($attributes, $sanitizeMap);
        
        return ConnectConfig::disconnect($attributes, true);
    }

    public function getWpPages()
    {
        $pages = wpFluent()->table('posts')
            ->select(['ID', 'post_title'])
            ->where('post_status', 'publish')
            ->where('post_type', 'page')
            ->orderBy('ID', 'ASC')
            ->get();

        wp_send_json_success([
            'pages' => $pages
        ]);
    }

    public function cancelSubscription()
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in route registration
        $request = wpFluentForm()->request;
        $attributes = $request->all();
        
        $sanitizeMap = [
            'subscription_id' => 'intval',
            'transaction_id' => 'intval',
            'submission_id' => 'intval',
        ];
        $attributes = fluentform_backend_sanitizer($attributes, $sanitizeMap);
        
        $subscriptionId = ArrayHelper::get($attributes, 'subscription_id');

        $subscription = fluentFormApi('submissions')->getSubscription($subscriptionId);

        if (!$subscription) {
            wp_send_json_error([
                'message' => __('Subscription could not be found', 'fluentform')
            ], 423);
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in route registration
        $transactionId = ArrayHelper::get($attributes, 'transaction_id', 0);

        // SECURITY (FINDING-29): bind the cancelled records to the AUTHORIZED SUBSCRIPTION itself,
        // not merely its form. The submission is derived from the subscription (not the request), and
        // the transaction must belong to that submission AND this subscription — so a per-form
        // payment manager cannot flip an unrelated subscription's transaction/submission from the
        // same form to cancelled. subscription_id is null on some legacy rows, so it is matched with
        // a null-safe fallback while submission_id (1:1 with the subscription) does the hard binding.
        $subscriptionSubmissionId = (int) $subscription->submission_id;

        $subscriptionTxnScope = function ($query) use ($subscription, $subscriptionSubmissionId) {
            return $query
                ->where('submission_id', $subscriptionSubmissionId)
                ->where(function ($q) use ($subscription) {
                    $q->where('subscription_id', $subscription->id)
                      ->orWhereNull('subscription_id')
                      ->orWhere('subscription_id', 0);
                });
        };

        // FINDING-29 + review #243: derive the subscription's OWN transaction from the authorized
        // scope. Prefer the request's transaction_id only when it resolves inside that scope; the id
        // is optional in the admin UI, so a missing/stale/mismatched value must not leave the local
        // transaction active while the submission and gateway are cancelled — fall back to the
        // subscription's transaction.
        $oldTransaction = $subscriptionTxnScope(
            $transactionId ? Transaction::where('id', $transactionId) : Transaction::query()
        )->first();

        if (!$oldTransaction) {
            $oldTransaction = $subscriptionTxnScope(Transaction::query())->first();
        }

        $oldSubmission = Submission::where('id', $subscriptionSubmissionId)->first();

        // CORRECTNESS (review #243): cancel at the gateway FIRST — it holds the authoritative state.
        // Only touch local records after it confirms, so a gateway failure never leaves us showing
        // "cancelled" locally while the subscription keeps charging.
        $response = (new PaymentManagement())->cancelSubscription($subscription);

        if (is_wp_error($response)) {
            wp_send_json_error([
                'message' => $response->get_error_code() . ' - ' . $response->get_error_message()
            ], 423);
        }

        // Gateway cancelled: reconcile each local record independently (so a partially-cancelled
        // state is completed rather than skipped) and atomically. The transaction stays scoped to
        // this subscription (FINDING-29), so an unrelated same-form transaction cannot be flipped.
        $now = current_time('mysql');
        wpFluent()->transaction(function () use ($subscriptionTxnScope, $subscriptionSubmissionId, $oldTransaction, $oldSubmission, $now) {
            if ($oldTransaction && $oldTransaction->status !== 'cancelled') {
                $subscriptionTxnScope(Transaction::where('id', $oldTransaction->id))
                    ->update([
                        'status'     => 'cancelled',
                        'updated_at' => $now
                    ]);
            }

            if ($oldSubmission && $oldSubmission->payment_status !== 'cancelled') {
                Submission::where('id', $subscriptionSubmissionId)
                    ->update([
                        'payment_status' => 'cancelled',
                        'updated_at'     => $now
                    ]);
            }
        });

        wp_send_json_success([
            'message' => $response
        ]);

    }

}
