<?php

namespace FluentCart\App\Modules\PaymentMethods\StripeGateway;

use FluentCart\App\Helpers\CurrenciesHelper;
use FluentCart\App\Models\Cart;
use FluentCart\App\Modules\PaymentMethods\StripeGateway\API\API;
use FluentCart\App\Services\Payments\PaymentInstance;
use FluentCart\App\Helpers\Helper;
use FluentCart\Framework\Support\Arr;

class Processor
{

    public function handleSubscription(PaymentInstance $paymentInstance, $paymentArgs)
    {
        $stripeSettings = new StripeSettingsBase();
        $checkoutMode = $stripeSettings->get('checkout_mode') ?? 'onsite';

        // If hosted mode, create Checkout Session for subscription
        if ($checkoutMode === 'hosted') {
            return $this->handleHostedSubscriptionCheckout($paymentInstance, $paymentArgs);
        }

        // Original onsite subscription flow
        $orderType = $paymentInstance->order->type;
        $fcCustomer = $paymentInstance->order->customer;
        $billingAddress = $paymentInstance->order->billing_address;

        $subscriptionModel = $paymentInstance->subscription;

        if (!$subscriptionModel) {
            return new \WP_Error('no_subscription', __('No subscription found.', 'fluent-cart'));
        }

        $stripeCustomer = StripeHelper::createOrGetStripeCustomer($paymentInstance->order->customer);

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

        $feeTotal = $orderType !== 'renewal' ? (int)$paymentInstance->order->fee_total : 0;
        $initialAmount = (int)$subscriptionModel->signup_fee + $paymentInstance->getExtraAddonAmount() + $feeTotal;

        if ($orderType == 'renewal') {
            $stripePlan = Plan::getStripePricing([
                'order_id'         => $subscriptionModel->parent_order_id,
                'product_id'       => $subscriptionModel->product_id,
                'variation_id'     => $subscriptionModel->variation_id,
                'billing_interval' => $subscriptionModel->billing_interval,
                'recurring_total'  => $subscriptionModel->getCurrentRenewalAmount(),
                'currency'         => $paymentInstance->order->currency,
                'trial_days'       => $subscriptionModel->getReactivationTrialDays(), // No trial for renewals
                'interval_count'   => 1 // per month / year / week
            ]);

            $initialAmount = 0;
        } else {
            $stripePlan = Plan::getStripePricing([
                'order_id'         => $subscriptionModel->parent_order_id,
                'product_id'       => $subscriptionModel->product_id,
                'variation_id'     => $subscriptionModel->variation_id,
                'billing_interval' => $subscriptionModel->billing_interval,
                'recurring_total'  => $subscriptionModel->recurring_total,
                'currency'         => $paymentInstance->order->currency,
                'trial_days'       => (int)$subscriptionModel->trial_days,
                'interval_count'   => 1 // per month / year / week
            ]);
        }

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

        $stripeSubscriptionData = [
            'customer'         => Arr::get($stripeCustomer, 'id', ''),
            'payment_behavior' => 'default_incomplete',
            'payment_settings' => [
                'save_default_payment_method' => 'on_subscription'
            ],
            'items'            => [
                [
                    'plan'     => $stripePlan['id'],
                    'quantity' => $subscriptionModel->quantity ?: 1,
                ]
            ],
            'expand'           => [
                'latest_invoice.confirmation_secret',
                'pending_setup_intent'
            ],
            'metadata'         => apply_filters('fluent_cart/payments/stripe_metadata_subscription', [
                'fct_ref_id' => $paymentInstance->order->uuid,
                'email'      => $paymentInstance->order->customer->email,
                'name'       => $paymentInstance->order->full_name,
                'subscription_item'       => $subscriptionModel->item_name,
                'order_reference' => 'fct_order_id_' . $paymentInstance->order->id,
            ], [
                'order'       => $paymentInstance->order,
                'transaction' => $paymentInstance->transaction,
                'subscription' => $subscriptionModel
            ]),
        ];

        if (Arr::get($stripePlan, 'trial_period_days')) {
            // Anchor trial_end to a STABLE point (the charge transaction's creation time,
            // which is preserved across re-submissions) instead of "now". A volatile
            // trial_end would make a retried create send a different body, and Stripe
            // rejects a reused Idempotency-Key whose parameters changed (400), bricking
            // the order for the key's 24h lifetime. Anchoring keeps retries byte-identical.
            $trialDays = (int) Arr::get($stripePlan, 'trial_period_days');
            $anchorTs = $paymentInstance->transaction && $paymentInstance->transaction->created_at
                ? strtotime($paymentInstance->transaction->created_at . ' UTC')
                : time();
            $trialEnd = strtotime('+' . $trialDays . ' days', $anchorTs);
            // Stripe requires trial_end in the future; only a stale late retry could fall
            // behind, and that order would already carry a fresh transaction/key anyway.
            if ($trialEnd <= time() + MINUTE_IN_SECONDS) {
                $trialEnd = strtotime('+' . $trialDays . ' days');
            }
            $stripeSubscriptionData['trial_end'] = $trialEnd;
        }

        // Maybe we have initial amount
        if ($initialAmount) {
            $addonPrice = Plan::getOneTimeAddonPrice([
                'product_id' => $subscriptionModel->product_id,
                'currency'   => $paymentInstance->order->currency,
                'amount'     => (int)$initialAmount,
                'variation_id'     => $subscriptionModel->variation_id,
                'order_id'         => $subscriptionModel->parent_order_id,
            ]);

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

            $stripeSubscriptionData['add_invoice_items'] = [
                [
                    'price'    => $addonPrice['id'],
                    'quantity' => 1
                ]
            ];
        }

        if ($expireAt = $paymentInstance->getSubscriptionCancelAtTimeStamp()) {
          //  $stripeSubscriptionData['cancel_at'] = $expireAt;
        }

        // Duplicate-charge defense — key construction contract in
        // .claude/skills/coding-rules/payment-idempotency.md. Seed dedupes duplicates
        // and frees retries; fingerprint = charge-material params so an edited order
        // gets a fresh key instead of a same-key/changed-parameters 400 (the abandoned
        // incomplete subscription auto-expires). Params, not transaction->total: a
        // recurring coupon can change the plan while the first charge stays $0.
        // Metadata excluded — volatile filters must not change the key on a duplicate.
        $idempotencyFingerprint = [
            'customer'          => Arr::get($stripeSubscriptionData, 'customer'),
            'items'             => Arr::get($stripeSubscriptionData, 'items'),
            'add_invoice_items' => Arr::get($stripeSubscriptionData, 'add_invoice_items'),
            'trial_end'         => Arr::get($stripeSubscriptionData, 'trial_end'),
        ];
        $idempotencySeed = $paymentInstance->getIdempotencySeed();
        $idempotencyKey = $idempotencySeed
            ? 'fct_stripe_sub_' . md5($idempotencySeed . '|' . wp_json_encode($idempotencyFingerprint))
            : null;

        $stripeSubscription = (new API())->createStripeObject('subscriptions', $stripeSubscriptionData, 'current', $idempotencyKey);

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

        $vendorChargeId = Arr::get($stripeSubscription, 'latest_invoice.payment_intent');
        if (!$vendorChargeId) {
            $vendorChargeId = Arr::get($stripeSubscription, 'pending_setup_intent.id');
        }

        if ($vendorChargeId) {
            $paymentInstance->transaction->update(['vendor_charge_id' => $vendorChargeId]);
        }

        $vendorSubscriptionId = Arr::get($stripeSubscription, 'id');

        $subscriptionModel->update([
            'vendor_subscription_id' => $vendorSubscriptionId,
            'vendor_customer_id'     => $stripeSubscription['customer']
        ]);

        if ($stripeSubscription['pending_setup_intent'] != null) {
            $paymentArgs['vendor_subscription_info'] = [
                'type'         => 'setup',
                'clientSecret' => Arr::get($stripeSubscription, 'pending_setup_intent.client_secret'),
                'trx_hash'     => $paymentInstance->transaction->uuid,
            ];
        } else {
            $paymentArgs['vendor_subscription_info'] = [
                'type'         => 'payment',
                'clientSecret' => Arr::get($stripeSubscription, 'latest_invoice.confirmation_secret.client_secret')
            ];
        }

        $customerData = [
            'name'      => $fcCustomer->first_name . ' ' . $fcCustomer->last_name,
            'email'     => $fcCustomer->email,
            'address_1' => $billingAddress->address_1,
            'address_2' => $billingAddress->address_2,
            'city'      => $billingAddress->city,
            'state'     => $billingAddress->state,
            'postcode'  => $billingAddress->postcode,
            'country'   => $billingAddress->country
        ];

        return [
            'nextAction'   => 'stripe',
            'actionName'   => 'custom',
            'status'       => 'success',
            'message'      => __('Order has been placed successfully', 'fluent-cart'),
            'payment_args' => $paymentArgs,
            'response'     => $stripeSubscription,
            'fc_customer'  => $customerData
        ];
    }


    /**
     * Handle single payment for stripe (onsite or hosted)
     *
     * @return \WP_Error|array
     */
    public function handleSinglePayment(PaymentInstance $paymentInstance, $paymentArgs = [])
    {
        $stripeSettings = new StripeSettingsBase();
        $checkoutMode = $stripeSettings->get('checkout_mode') ?? 'onsite';

        if ($checkoutMode === 'hosted') {
            return $this->handleHostedCheckout($paymentInstance, $paymentArgs);
        }

        // Original onsite payment flow
        $order = $paymentInstance->order;
        $transaction = $paymentInstance->transaction;
        $fcCustomer = $paymentInstance->order->customer;
        $billingAddress = $order->billing_address;

        $transactionCurrency = $transaction->currency;
        $intentAmount = (int)$transaction->total;

        if ($transactionCurrency && CurrenciesHelper::isZeroDecimal($transactionCurrency)) {
            $intentAmount = (int)($intentAmount / 100);
        }

        $intentData = [
            'amount'                    => $intentAmount,
            'currency'                  => $transactionCurrency,
            'automatic_payment_methods' => ['enabled' => 'true'],
            'metadata'                  => apply_filters('fluent_cart/payments/stripe_metadata_onetime', [
                'fct_ref_id' => $order->uuid,
                'Name'       => $order->customer->full_name,
                'Email'      => $order->customer->email,
                'order_reference' => 'fct_order_id_' . $paymentInstance->order->id,
            ], [
                'order'       => $order,
                'transaction' => $transaction
            ]),
        ];

        $itemCount = 1;
        foreach($paymentInstance->order->order_items as $item) {
            $intentData['metadata']['item ' . $itemCount] = 'Name: ' . $item->title . ', ' . 'Qty: ' . $item->quantity . ', Price: ' . Helper::toDecimal($item->line_total, false, null, true, true, false);
            if (count($intentData['metadata']) > 49) {
                break;
            }
            $itemCount++;
        }

        if (!empty($paymentArgs['customer'])) {
            $intentData['customer'] = $paymentArgs['customer'];
        } else {
            $stripeCustomer = StripeHelper::createOrGetStripeCustomer($order->customer);
            if (is_wp_error($stripeCustomer)) {
                return $stripeCustomer;
            }
            $intentData['customer'] = $stripeCustomer['id'];
        }

        if (!empty($paymentArgs['setup_future_usage'])) {
            $intentData['setup_future_usage'] = $paymentArgs['setup_future_usage'];
        }

        $paymentArgs['public_key'] = (new StripeSettingsBase())->getPublicKey();

        $intentData = apply_filters('fluent_cart/payments/stripe_onetime_intent_args', $intentData, [
            'order'       => $order,
            'transaction' => $transaction
        ]);

        // Same duplicate-charge defense for one-time onsite payments. Customer is in
        // the fingerprint because a guest editing their email between attempts maps to
        // a different Stripe customer — same key there would 400 for the key's 24h
        // lifetime. Built AFTER the intent-args filter so filtered amounts are what
        // get fingerprinted.
        $idempotencyFingerprint = [
            'amount'   => Arr::get($intentData, 'amount'),
            'currency' => Arr::get($intentData, 'currency'),
            'customer' => Arr::get($intentData, 'customer'),
        ];
        $idempotencySeed = $paymentInstance->getIdempotencySeed();
        $idempotencyKey = $idempotencySeed
            ? 'fct_stripe_pi_' . md5($idempotencySeed . '|' . wp_json_encode($idempotencyFingerprint))
            : null;

        $intent = (new API())->createStripeObject('payment_intents', $intentData, 'current', $idempotencyKey);

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

        $transaction->update([
            'vendor_charge_id' => $intent['id']
        ]);

        $customerData = [
            'name'      => $fcCustomer->first_name . ' ' . $fcCustomer->last_name,
            'email'     => $fcCustomer->email,
            'address_1' => $billingAddress->address_1,
            'address_2' => $billingAddress->address_2,
            'city'      => $billingAddress->city,
            'state'     => $billingAddress->state,
            'postcode'  => $billingAddress->postcode,
            'country'   => $billingAddress->country
        ];

        return [
            'status'       => 'success',
            'nextAction'   => 'stripe',
            'actionName'   => 'custom',
            'message'      => __('Order has been placed successfully', 'fluent-cart'),
            'response'     => $intent,
            'payment_args' => $paymentArgs,
            'fc_customer'  => $customerData
        ];
    }


    private function handleHostedCheckout(PaymentInstance $paymentInstance, $paymentArgs = [])
    {
        $order = $paymentInstance->order;
        $transaction = $paymentInstance->transaction;
        $fcCustomer = $order->customer;
        $billingAddress = $order->billing_address;

        $transactionCurrency = $transaction->currency;
        $chargeAmount = (int)$transaction->total;

        if ($transactionCurrency && CurrenciesHelper::isZeroDecimal($transactionCurrency)) {
            $chargeAmount = (int)($chargeAmount / 100);
        }

        // Create or get Stripe customer
        $stripeCustomer = StripeHelper::createOrGetStripeCustomer($fcCustomer);
        if (is_wp_error($stripeCustomer)) {
            return $stripeCustomer;
        }

        // Use a single line item with the total amount to avoid complexity
        // This is simpler and prevents any calculation mismatches
        $storeName = (new \FluentCart\Api\StoreSettings())->get('store_name');
        $lineItems = [
            [
                'price_data' => [
                    'currency'     => strtolower($transactionCurrency),
                    'product_data' => [
                        'name'        => $storeName . ' - Order #' . $order->uuid,
                        'description' => sprintf(__('Order total including all items, shipping (If any), and taxes (If any)', 'fluent-cart')),
                    ],
                    'unit_amount'  => $chargeAmount,
                ],
                'quantity'   => 1,
            ]
        ];

        $sessionData = [
            'customer'           => $stripeCustomer['id'],
            'client_reference_id' => $order->uuid,
            'line_items'         => $lineItems,
            'mode'               => 'payment',
            'success_url'        => Arr::get($paymentArgs, 'success_url') . '&fct_stripe_hosted=1&trx_hash=' . $transaction->uuid,
            'cancel_url'         => StripeHelper::getCancelUrl(),
            'metadata'           => [
                'fct_ref_id'      => $order->uuid,
                'transaction_hash' => $transaction->uuid,
                'order_reference' => 'fct_order_id_' . $order->id,
            ],
        ];

        $itemCount = 1;
        foreach($order->order_items as $item) {
            $sessionData['metadata']['item ' . $itemCount] = 'Name: ' . $item->title . ', ' . 'Qty: ' . $item->quantity . ', Price: ' . Helper::toDecimal($item->line_total, false, null, true, true, false);
            if (count($sessionData['metadata']) > 49) {
                break;
            }
            
            $itemCount++;
        }

        $sessionData = apply_filters('fluent_cart/payments/stripe_checkout_session_args', $sessionData, [
            'order'       => $order,
            'transaction' => $transaction
        ]);

        // Same duplicate-charge defense as every other Stripe create path: a pure
        // duplicate replays the key and gets the original session back; an edited-cart
        // resubmit gets a fresh key instead of a same-key/changed-parameters 400.
        $idempotencyFingerprint = [
            'customer'   => Arr::get($sessionData, 'customer'),
            'line_items' => Arr::get($sessionData, 'line_items'),
            'mode'       => Arr::get($sessionData, 'mode'),
        ];
        $idempotencySeed = $paymentInstance->getIdempotencySeed();
        $idempotencyKey = $idempotencySeed
            ? 'fct_stripe_cs_' . md5($idempotencySeed . '|' . wp_json_encode($idempotencyFingerprint))
            : null;

        $session = (new API())->createStripeObject('checkout/sessions', $sessionData, 'current', $idempotencyKey);

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

        $transaction->update([
            'meta'             => array_merge($transaction->meta ?? [], [
                'session_id' => $session['id']
            ])
        ]);

        return [
            'status'       => 'success',
            'nextAction'   => 'stripe',
            'actionName'   => 'redirect',
            'message'      => __('Redirecting to Stripe checkout...', 'fluent-cart'),
            'response'     => $session,
            'payment_args' => array_merge($paymentArgs, [
                'checkout_url' => $session['url'],
                'session_id'   => $session['id']
            ])
        ];
    }


    private function handleHostedSubscriptionCheckout(PaymentInstance $paymentInstance, $paymentArgs = [])
    {
        $order = $paymentInstance->order;
        $transaction = $paymentInstance->transaction;
        $subscriptionModel = $paymentInstance->subscription;
        $fcCustomer = $order->customer;

        if (!$subscriptionModel) {
            return new \WP_Error('no_subscription', __('No subscription found.', 'fluent-cart'));
        }

        $transactionCurrency = $transaction->currency;
        $orderType = $order->type;

        // Create or get Stripe customer
        $stripeCustomer = StripeHelper::createOrGetStripeCustomer($fcCustomer);
        if (is_wp_error($stripeCustomer)) {
            return $stripeCustomer;
        }

        // Get or create Stripe price/plan
        if ($orderType == 'renewal') {
            $stripePlan = Plan::getStripePricing([
                'product_id'       => $subscriptionModel->product_id,
                'variation_id'     => $subscriptionModel->variation_id,
                'billing_interval' => $subscriptionModel->billing_interval,
                'recurring_total'  => $subscriptionModel->getCurrentRenewalAmount(),
                'currency'         => $order->currency,
                'trial_days'       => $subscriptionModel->getReactivationTrialDays(),
                'interval_count'   => 1,
                'order_id'         => $subscriptionModel->parent_order_id,
            ]);
        } else {
            $stripePlan = Plan::getStripePricing([
                'product_id'       => $subscriptionModel->product_id,
                'variation_id'     => $subscriptionModel->variation_id,
                'billing_interval' => $subscriptionModel->billing_interval,
                'recurring_total'  => $subscriptionModel->recurring_total,
                'currency'         => $order->currency,
                'trial_days'       => (int)$subscriptionModel->trial_days,
                'interval_count'   => 1,
                'order_id'         => $subscriptionModel->parent_order_id,
            ]);
        }

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


        $feeTotal = $orderType !== 'renewal' ? (int)$paymentInstance->order->fee_total : 0;
        $initialAmount = (int)$subscriptionModel->signup_fee + $paymentInstance->getExtraAddonAmount() + $feeTotal;

        if ($orderType == 'renewal') {
            $initialAmount = 0;
        }

        $recurringTotal = (int)$subscriptionModel->recurring_total;
        if ($transactionCurrency && CurrenciesHelper::isZeroDecimal($transactionCurrency)) {
            $initialAmount = (int)($initialAmount / 100);
            $recurringTotal = (int)($recurringTotal / 100);
        }

        $lineItems = [
            [
                'price'    => $stripePlan['id'],
                'quantity' => $subscriptionModel->quantity ?: 1,
            ]
        ];

        $subscriptionData = [
            'metadata' => [
                'fct_ref_id'      => $order->uuid,
                'email'           => $fcCustomer->email,
                'name'            => $order->full_name,
                'order_reference' => 'fct_order_id_' . $order->id,
                'subscription_item'            => $subscriptionModel->item_name,
            ],
        ];

        // Handle trial period if set in plan (same as onsite lines 94-96)
        if (!empty($stripePlan['trial_period_days'])) {
            $subscriptionData['trial_period_days'] = $stripePlan['trial_period_days'];
        }

        if ($initialAmount > 0) {
            $addonPrice = Plan::getOneTimeAddonPrice([
                'product_id' => $subscriptionModel->product_id,
                'currency'   => $order->currency,
                'amount'     => (int)$initialAmount,
                'name'       => __('Signup fee / initial payment', 'fluent-cart'),
                'variation_id'     => $subscriptionModel->variation_id,
                'order_id'         => $subscriptionModel->parent_order_id,

            ]);

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

            $lineItems[] = [
                'price'    => $addonPrice['id'],
                'quantity' => 1
            ];
        }

        $sessionData = [
            'customer'            => $stripeCustomer['id'],
            'client_reference_id' => $order->uuid,
            'line_items'          => $lineItems,
            'mode'                => 'subscription',
            'consent_collection' => ['payment_method_reuse_agreement' => ['position' => 'hidden']],
            'success_url'         => Arr::get($paymentArgs, 'success_url') . '&fct_stripe_hosted=1&trx_hash=' . $transaction->uuid,
            'cancel_url'          => StripeHelper::getCancelUrl(),
            'subscription_data'   => $subscriptionData,
            'metadata'            => [
                'fct_ref_id'         => $order->uuid,
                'subscription_item'  => $subscriptionModel->item_name,
                'transaction_hash'   => $transaction->uuid,
                'order_reference'    => 'fct_order_id_' . $order->id,
            ],
        ];

        $sessionData = apply_filters('fluent_cart/payments/stripe_subscription_checkout_session_args', $sessionData, [
            'order'        => $order,
            'transaction'  => $transaction,
            'subscription' => $subscriptionModel
        ]);

        // Same duplicate-subscription defense as the onsite path, applied to the hosted
        // Checkout Session. Metadata is excluded so a volatile metadata filter cannot
        // change the key on a genuine duplicate and reopen the double-charge window.
        $idempotencyFingerprint = [
            'customer'          => Arr::get($sessionData, 'customer'),
            'line_items'        => Arr::get($sessionData, 'line_items'),
            'mode'              => Arr::get($sessionData, 'mode'),
            'subscription_data' => Arr::get($sessionData, 'subscription_data'),
        ];
        $idempotencySeed = $paymentInstance->getIdempotencySeed();
        $idempotencyKey = $idempotencySeed
            ? 'fct_stripe_sub_cs_' . md5($idempotencySeed . '|' . wp_json_encode($idempotencyFingerprint))
            : null;

        $session = (new API())->createStripeObject('checkout/sessions', $sessionData, 'current', $idempotencyKey);

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

        $subscriptionModel->update([
            'vendor_customer_id'     => $stripeCustomer['id']
        ]);

        $transaction->update([
            'vendor_charge_id' => Arr::get($session, 'payment_intent', Arr::get($session, 'id')),
            'meta'             => array_merge($transaction->meta ?? [], [
                'session_id' => $session['id']
            ])
        ]);

        return [
            'status'       => 'success',
            'nextAction'   => 'stripe',
            'actionName'   => 'redirect',
            'message'      => __('Redirecting to Stripe checkout...', 'fluent-cart'),
            'response'     => $session,
            'payment_args' => array_merge($paymentArgs, [
                'checkout_url' => $session['url'],
                'session_id'   => $session['id']
            ])
        ];
    }

}
