<?php

namespace GriffinForms\Includes\Payments;

use GriffinForms\Traits\Singleton;
use GriffinForms\Includes\Integrations\FormSettingsManager;
use GriffinForms\Includes\Helpers\Currency;
use GriffinForms\Settings;
use WP_Error;

/**
 * Handles Stripe PaymentIntent orchestration.
 */
class StripeIntentService extends \GriffinForms\Format
{
    use Singleton;

    /**
     * @var SubmissionPaymentMeta
     */
    protected $payment_meta;

    /**
     * @var FormSettingsManager
     */
    protected $form_settings_manager;

    /**
     * @var Currency
     */
    protected $currency_helper;

    protected function setPaymentmeta(): void
    {
        $this->payment_meta = SubmissionPaymentMeta::getInstance();
    }

    protected function setFormsettingsmanager(): void
    {
        $this->form_settings_manager = FormSettingsManager::getInstance();
    }

    protected function setCurrencyhelper(): void
    {
        $this->currency_helper = Currency::getInstance();
    }

    /**
     * Shared gateway contract wrapper for create.
     *
     * @param array $payload
     * @return array|WP_Error
     */
    public function createSession(array $payload)
    {
        return $this->createIntent($payload);
    }

    /**
     * Shared gateway contract wrapper for resume.
     *
     * @param int $submission_id
     * @param string $cart_hash
     * @param string $amount_hash
     * @param array $cart
     * @return array|WP_Error
     */
    public function resumeSession(int $submission_id, string $cart_hash, string $amount_hash = '', array $cart = array())
    {
        return $this->resumeIntent($submission_id, $cart_hash, $amount_hash, $cart);
    }

    /**
     * Shared gateway contract wrapper for completion.
     *
     * @param int $submission_id
     * @param array $payload
     * @return bool|WP_Error
     */
    public function completeSession(int $submission_id, array $payload)
    {
        return $this->completeIntent($submission_id, $payload);
    }

    /**
     * Build or reuse a Stripe PaymentIntent.
     *
     * @param array $payload
     * @return array|WP_Error
     */
    public function createIntent(array $payload)
    {
        $required = ['form_id', 'submission_id', 'cart', 'cart_hash', 'amount_hash', 'gateway'];
        foreach ($required as $key) {
            if (!isset($payload[$key])) {
                return new WP_Error('missing_param', sprintf(__('Missing parameter: %s', 'griffinforms-form-builder'), $key));
            }
        }

        $secret_key = $this->settings->getOption('stripe_secret_key');
        $publishable_key = $this->settings->getOption('stripe_publishable_key');
        if (empty($secret_key) || empty($publishable_key)) {
            return new WP_Error('stripe_not_configured', __('Stripe is not fully configured.', 'griffinforms-form-builder'));
        }

        if (empty($payload['cart']['items'])) {
            return new WP_Error('empty_cart', __('No products were selected for payment.', 'griffinforms-form-builder'));
        }

        $total = isset($payload['cart']['total']) ? (float) $payload['cart']['total'] : 0;
        if ($total <= 0) {
            return new WP_Error('invalid_total', __('Payment total must be greater than zero.', 'griffinforms-form-builder'));
        }

        $currency = strtolower($payload['cart']['currency'] ?? $this->currency_helper->sanitizeCurrency(null));
        $amount_minor = $this->convertToMinorUnits($total, $currency);
        if ($amount_minor < 1) {
            return new WP_Error('invalid_amount', __('Unable to determine payment amount.', 'griffinforms-form-builder'));
        }

        $meta = $this->payment_meta->getMeta((int) $payload['submission_id']);
        $existing = $meta['stripe'] ?? [];
        if (empty($payload['force_new']) && !empty($existing['intent_id']) && ($existing['cart_hash'] ?? '') === $payload['cart_hash']) {
            $resume = $this->resumeIntent((int) $payload['submission_id'], $payload['cart_hash'], $payload['amount_hash'], $payload['cart']);
            if (!is_wp_error($resume)) {
                return $resume;
            }
        }

        $stripe = new \Stripe\StripeClient($secret_key);

        $billing_details = $this->buildBillingDetails($payload);
        $metadata = $this->buildMetadata($payload);

        try {
            $intent_params = [
                'amount'   => $amount_minor,
                'currency' => $currency,
                'metadata' => $metadata,
                'automatic_payment_methods' => ['enabled' => true],
            ];

            if (!empty($billing_details['email'])) {
                $intent_params['receipt_email'] = $billing_details['email'];
            }

            if (!empty($billing_details['shipping'])) {
                $intent_params['shipping'] = $billing_details['shipping'];
            }

            $intent = $stripe->paymentIntents->create($intent_params);
        } catch (\Exception $e) {
            $message = sprintf('Stripe createIntent error: %s', $e->getMessage());
            $this->log->add('error', 'submission', (int) $payload['submission_id'], get_current_user_id(), $message, 'Payment Integration (stripe)');
            return new WP_Error('stripe_error', __('Unable to initialize Stripe payment.', 'griffinforms-form-builder'));
        }

        $meta['stripe'] = [
            'intent_id'      => $intent->id,
            'client_secret'  => $intent->client_secret,
            'amount'         => $amount_minor,
            'display_amount' => $total,
            'currency'       => $currency,
            'cart_hash'      => $payload['cart_hash'],
            'amount_hash'    => $payload['amount_hash'],
            'status'         => $intent->status,
            'gateway'        => 'stripe',
            'updated_at'     => current_time('timestamp', true),
        ];
        $this->payment_meta->saveMeta((int) $payload['submission_id'], $meta);

        return [
            'intent_id'        => $intent->id,
            'client_secret'    => $intent->client_secret,
            'publishable_key'  => $publishable_key,
            'amount'           => $amount_minor,
            'display_amount'   => $total,
            'currency'         => strtoupper($currency),
            'cart'             => [
                'subtotal'  => $payload['cart']['subtotal'],
                'tax_total' => $payload['cart']['tax_total'],
                'tax_lines' => $payload['cart']['tax_lines'],
                'total'     => $payload['cart']['total'],
            ],
            'customer' => [
                'name'   => $billing_details['name'] ?? '',
                'email'  => $billing_details['email'] ?? '',
                'phone'  => $billing_details['phone'] ?? '',
                'address'=> $billing_details['shipping']['address'] ?? [],
            ],
        ];
    }

    /**
     * Resume an existing intent and fetch latest client secret.
     */
    public function resumeIntent(int $submission_id, string $cart_hash, string $amount_hash = '', array $cart = array())
    {
        $meta = $this->payment_meta->getMeta($submission_id);
        if (empty($meta['stripe']['intent_id']) || ($meta['stripe']['cart_hash'] ?? '') !== $cart_hash) {
            return new WP_Error('intent_missing', __('No matching payment intent found to resume.', 'griffinforms-form-builder'));
        }
        if ($amount_hash !== '' && ($meta['stripe']['amount_hash'] ?? '') !== $amount_hash) {
            return new WP_Error('intent_mismatch', __('Payment amount changed. Please review and try again.', 'griffinforms-form-builder'));
        }

        $secret_key = $this->settings->getOption('stripe_secret_key');
        $publishable_key = $this->settings->getOption('stripe_publishable_key');
        if (empty($secret_key) || empty($publishable_key)) {
            return new WP_Error('stripe_not_configured', __('Stripe is not fully configured.', 'griffinforms-form-builder'));
        }

        $stripe = new \Stripe\StripeClient($secret_key);

        try {
            $intent = $stripe->paymentIntents->retrieve($meta['stripe']['intent_id']);
        } catch (\Exception $e) {
            $message = sprintf('Stripe resumeIntent error: %s', $e->getMessage());
            $this->log->add('error', 'submission', $submission_id, get_current_user_id(), $message, 'Payment Integration (stripe)');
            return new WP_Error('stripe_error', __('Unable to resume Stripe payment.', 'griffinforms-form-builder'));
        }

        // Allow reuse only when intent is still confirmable.
        $resumable_statuses = array('requires_payment_method', 'requires_confirmation', 'requires_action', 'processing', 'requires_capture');
        $is_resumable = in_array($intent->status, $resumable_statuses, true);

        if ($is_resumable) {
            $meta['stripe']['client_secret'] = $intent->client_secret;
            $meta['stripe']['status'] = $intent->status;
            $meta['stripe']['updated_at'] = current_time('timestamp', true);
            $this->payment_meta->saveMeta($submission_id, $meta);

            return [
                'intent_id'       => $intent->id,
                'client_secret'   => $intent->client_secret,
                'publishable_key' => $publishable_key,
                'amount'          => $meta['stripe']['amount'] ?? 0,
                'display_amount'  => $meta['stripe']['display_amount'] ?? 0,
                'currency'        => strtoupper($meta['stripe']['currency'] ?? 'USD'),
                'customer'        => [],
            ];
        }

        // If not resumable, attempt to create a fresh intent using the current cart.
        if (empty($cart['items'])) {
            return new WP_Error('intent_not_resumable', __('This payment link has expired. Please restart payment from the form.', 'griffinforms-form-builder'));
        }

        $this->log->add(
            'info',
            'submission',
            $submission_id,
            get_current_user_id(),
            sprintf(
                'Stripe intent %s not resumable (status: %s); creating new intent for submission #%d.',
                $intent->id,
                $intent->status,
                $submission_id
            ),
            'Payment Integration (stripe)'
        );

        $hashes = \GriffinForms\Includes\Payments\CartHasher::getInstance()->compute($cart, 'stripe');
        $rebuild = $this->createIntent([
            'form_id'        => (int) ($meta['stripe']['form_id'] ?? 0) ?: 0,
            'submission_id'  => $submission_id,
            'cart'           => $cart,
            'cart_hash'      => $hashes['cart_hash'],
            'amount_hash'    => $hashes['amount_hash'],
            'gateway'        => 'stripe',
            'field_values'   => $cart['field_values'] ?? array(),
            'field_types'    => $cart['field_types'] ?? array(),
            'force_new'      => true,
        ]);

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

        return $rebuild;
    }

    /**
     * Verify completion and update submission status.
     */
    public function completeIntent(int $submission_id, array $payload)
    {
        $meta = $this->payment_meta->getMeta($submission_id);
        if (empty($meta['stripe']['intent_id'])) {
            return new WP_Error('intent_missing', __('Payment intent not found for this submission.', 'griffinforms-form-builder'));
        }

        $intent_id = sanitize_text_field($payload['intent_id'] ?? '');
        if ($intent_id !== $meta['stripe']['intent_id']) {
            return new WP_Error('intent_mismatch', __('Intent mismatch detected.', 'griffinforms-form-builder'));
        }

        $secret_key = $this->settings->getOption('stripe_secret_key');
        if (empty($secret_key)) {
            return new WP_Error('stripe_not_configured', __('Stripe is not fully configured.', 'griffinforms-form-builder'));
        }

        $stripe = new \Stripe\StripeClient($secret_key);

        try {
            $intent = $stripe->paymentIntents->retrieve($intent_id);
        } catch (\Exception $e) {
            $message = sprintf('Stripe confirmIntent error: %s', $e->getMessage());
            $this->log->add('error', 'submission', $submission_id, get_current_user_id(), $message, 'Payment Integration (stripe)');
            return new WP_Error('stripe_error', __('Unable to verify Stripe payment.', 'griffinforms-form-builder'));
        }

        $status_manager = SubmissionStatusManager::getInstance();
        $context = [
            'gateway'    => 'stripe',
            'intent_id'  => $intent_id,
            'amount'     => $meta['stripe']['display_amount'] ?? 0,
            'currency'   => $meta['stripe']['currency'] ?? 'usd',
        ];

        if ($intent->status === 'succeeded') {
            $status_manager->markPaid($submission_id, $context);
        } elseif ($intent->status === 'requires_payment_method' || $intent->status === 'canceled') {
            $status_manager->markFailed($submission_id, $context);
        }

        $meta['stripe']['status'] = $intent->status;
        $meta['stripe']['updated_at'] = current_time('timestamp', true);
        $this->payment_meta->saveMeta($submission_id, $meta);

        if ($intent->status !== 'succeeded') {
            return new WP_Error('intent_incomplete', __('Stripe did not confirm the payment yet.', 'griffinforms-form-builder'));
        }

        return true;
    }

    /**
     * Derive billing/shipping details for Stripe.
     */
    protected function buildBillingDetails(array $payload): array
    {
        $details = [
            'email'   => '',
            'name'    => '',
            'phone'   => '',
            'shipping'=> [],
        ];

        $form_settings = $this->form_settings_manager->getIntegrationSettings((int) $payload['form_id'], 'stripe');
        $mapping = is_array($form_settings['settings'] ?? null) ? $form_settings['settings'] : [];
        $values = $payload['field_values'] ?? [];

        $details['email'] = $this->extractScalarValue($values, $mapping['customer_email_field'] ?? '');
        $details['name']  = $this->extractScalarValue($values, $mapping['customer_name_field'] ?? '');
        $details['phone'] = $this->extractScalarValue($values, $mapping['customer_phone_field'] ?? '');

        $address = $this->extractAddress($values, $mapping);
        if (!empty($address)) {
            $details['shipping'] = [
                'name'    => $details['name'] ?: $details['email'],
                'address' => $address,
            ];
        }

        // Drop empty entries so Stripe does not receive blank values when mapping is set to "None".
        return array_filter(
            $details,
            function ($value) {
                if (is_array($value)) {
                    return !empty($value);
                }
                return $value !== '' && $value !== null;
            }
        );
    }

    /**
     * Build Stripe metadata payload with useful context.
     *
     * @param array $payload
     * @return array
     */
    private function buildMetadata(array $payload): array
    {
        $form_id       = (int) ($payload['form_id'] ?? 0);
        $submission_id = (int) ($payload['submission_id'] ?? 0);
        $cart          = $payload['cart'] ?? [];
        $tax_lines     = is_array($cart['tax_lines'] ?? null) ? $cart['tax_lines'] : [];
        $tax_total     = isset($cart['tax_total']) ? (float) $cart['tax_total'] : 0.0;

        $form_url = $form_id ? admin_url('admin.php?page=griffinforms-app&form_id=' . $form_id) : '';
        $submission_url = $submission_id ? admin_url('admin.php?page=griffinforms-submission&id=' . $submission_id) : '';

        $field_ids = [];
        $products_meta = [];
        if (!empty($cart['items']) && is_array($cart['items'])) {
            foreach ($cart['items'] as $entry) {
                if (isset($entry['field_id'])) {
                    $field_ids[] = (int) $entry['field_id'];
                }
                $sku = isset($entry['product_id']) ? sanitize_text_field($entry['product_id']) : '';
                if ($sku === '') {
                    continue;
                }
                $name = isset($entry['label']) ? sanitize_text_field($entry['label']) : '';
                if (function_exists('mb_substr')) {
                    $name = mb_substr($name, 0, 50);
                } else {
                    $name = substr($name, 0, 50);
                }
                $qty  = isset($entry['quantity']) ? absint($entry['quantity']) : 1;
                if ($qty < 1) {
                    $qty = 1;
                }
                $products_meta[] = sprintf('%s x %d (%s)', $sku, $qty, $name);
            }
        }
        $field_ids = $field_ids ? array_values(array_unique(array_filter($field_ids))) : [];
        sort($field_ids);
        $field_ids_string = $field_ids ? implode(',', $field_ids) : '';
        $products_string = $products_meta ? implode(', ', $products_meta) : '';
        if (strlen($products_string) > 450) {
            $products_string = substr($products_string, 0, 450);
        }

        $tax_meta = '';
        if (!empty($tax_lines)) {
            $first = $tax_lines[0];
            $type   = isset($first['type']) ? sanitize_key($first['type']) : '';
            $rate   = isset($first['value']) ? (float) $first['value'] : 0;
            $amount = isset($first['amount']) ? (float) $first['amount'] : $tax_total;
            $tax_meta = sprintf('count=%d;type=%s;rate=%s;amount=%s', count($tax_lines), $type, $rate, $amount);
        } elseif ($tax_total > 0) {
            $tax_meta = sprintf('count=0;amount=%s', $tax_total);
        }

        $user_fragment = is_user_logged_in() ? (string) get_current_user_id() : 'Guest';

        $metadata = [
            'griffinforms_form_id'        => $form_id,
            'griffinforms_submission_id'  => $submission_id,
            'griffinforms_form_url'       => $form_url,
            'griffinforms_submission_url' => $submission_url,
            'griffinforms_wp_user_id'     => $user_fragment,
        ];

        if ($field_ids_string !== '') {
            $metadata['griffinforms_payment_fields'] = $field_ids_string;
        }
        if ($tax_meta !== '') {
            $metadata['griffinforms_tax'] = $tax_meta;
        }
        if ($products_string !== '') {
            $metadata['griffinforms_purchased_products'] = $products_string;
        }

        return $metadata;
    }

    /**
     * Try to extract an address array from mapped fields.
     */
    protected function extractAddress(array $values, array $mapping): array
    {
        $address_fields = [
            'line1' => $mapping['billing_address_line1_field'] ?? '',
            'line2' => $mapping['billing_address_line2_field'] ?? '',
            'city'  => $mapping['billing_city_field'] ?? '',
            'state' => $mapping['billing_state_field'] ?? '',
            'postal_code' => $mapping['billing_postal_code_field'] ?? '',
            'country' => $mapping['billing_country_field'] ?? '',
        ];

        $address = [
            'line1'       => '',
            'line2'       => '',
            'city'        => '',
            'state'       => '',
            'postal_code' => '',
            'country'     => '',
        ];

        $compound = $this->extractCompoundAddress($values, $address_fields['line1']);
        if (!empty($compound)) {
            return $this->cleanAddressPayload(array_merge($address, $compound));
        }

        foreach ($address_fields as $key => $field_id) {
            $address[$key] = $this->extractScalarValue($values, $field_id);
        }

        return $this->cleanAddressPayload($address);
    }

    /**
     * If the mapped field is an address field, extract structured parts.
     */
    protected function extractCompoundAddress(array $values, string $field_id): array
    {
        $field_id = (string) $field_id;
        if ($field_id === '' || empty($values[$field_id])) {
            return [];
        }

        $value = $values[$field_id];
        if (is_array($value) && isset($value['line1'])) {
            return $this->cleanAddressPayload([
                'line1'       => sanitize_text_field($value['line1']),
                'line2'       => sanitize_text_field($value['line2'] ?? ''),
                'city'        => sanitize_text_field($value['city'] ?? ''),
                'state'       => sanitize_text_field($value['state'] ?? ''),
                'postal_code' => sanitize_text_field($value['postal'] ?? ''),
                'country'     => strtoupper(sanitize_text_field($value['country'] ?? '')),
            ]);
        }

        return [];
    }

    /**
     * Fetch first scalar value from submission map.
     */
    protected function extractScalarValue(array $values, string $field_id): string
    {
        $field_id = (string) $field_id;
        if ($field_id === '' || !isset($values[$field_id])) {
            return '';
        }

        $value = $values[$field_id];
        if (is_array($value) && isset($value['items'])) {
            return '';
        }
        if (!is_array($value)) {
            return sanitize_text_field((string) $value);
        }

        if (isset($value['line1'])) {
            return sanitize_text_field($value['line1']);
        }

        foreach ($value as $entry) {
            if (is_array($entry)) {
                $result = $this->extractScalarValue(['tmp' => $entry], 'tmp');
                if ($result !== '') {
                    return $result;
                }
                continue;
            }

            $entry = trim((string) $entry);
            if ($entry !== '') {
                return sanitize_text_field($entry);
            }
        }

        return '';
    }

    /**
     * Remove empty address values before sending to Stripe.
     */
    protected function cleanAddressPayload(array $address): array
    {
        $filtered = array_filter($address, function ($value) {
            return $value !== '' && $value !== null;
        });

        return $filtered;
    }

    /**
     * Convert float amount to currency minor units.
     */
    protected function convertToMinorUnits(float $amount, string $currency): int
    {
        $currency = strtoupper($currency);
        $zero_decimals = ['BIF','CLP','DJF','GNF','JPY','KMF','KRW','MGA','PYG','RWF','UGX','VND','VUV','XAF','XOF','XPF'];
        $three_decimals = ['BHD','JOD','KWD','OMR'];

        if (in_array($currency, $zero_decimals, true)) {
            return (int) round($amount);
        }

        if (in_array($currency, $three_decimals, true)) {
            return (int) round($amount * 1000);
        }

        return (int) round($amount * 100);
    }
}
