<?php

namespace GriffinForms\Includes\Payments;

use GriffinForms\Traits\Singleton;
use GriffinForms\Includes\Helpers\Currency;
use WP_Error;

/**
 * Handles PayPal Orders API orchestration.
 */
class PaypalOrderService extends \GriffinForms\Format
{
    use Singleton;

    protected $payment_meta;
    protected $currency_helper;

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

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

    public function createSession(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));
            }
        }

        if (!$this->isConfigured()) {
            return new WP_Error('paypal_not_configured', __('PayPal 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'));
        }

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

        $order_request = $this->buildOrderPayload($payload);
        $request_id = 'gf-create-' . (int) $payload['submission_id'] . '-' . substr((string) $payload['cart_hash'], 0, 20);
        $response = $this->apiRequest('POST', '/v2/checkout/orders', $order_request, [
            'PayPal-Request-Id' => $request_id,
        ]);

        if (is_wp_error($response)) {
            $this->log->add(
                'error',
                'submission',
                (int) $payload['submission_id'],
                get_current_user_id(),
                'PayPal createOrder error: ' . $response->get_error_message(),
                'Payment Integration (paypal)'
            );
            return new WP_Error('paypal_error', $this->getUserSafeApiErrorMessage($response, __('Unable to initialize PayPal checkout.', 'griffinforms-form-builder')));
        }

        $order_id = sanitize_text_field($response['id'] ?? '');
        if ($order_id === '') {
            return new WP_Error('paypal_order_missing', __('Unable to initialize PayPal checkout.', 'griffinforms-form-builder'));
        }

        $currency = strtoupper((string) ($payload['cart']['currency'] ?? $this->currency_helper->sanitizeCurrency('USD')));
        $mode = $this->getMode();
        $meta['paypal'] = [
            'order_id'        => $order_id,
            'amount'          => $this->formatDecimal($total),
            'display_amount'  => $total,
            'currency'        => strtolower($currency),
            'cart_hash'       => $payload['cart_hash'],
            'amount_hash'     => $payload['amount_hash'],
            'status'          => sanitize_text_field($response['status'] ?? 'CREATED'),
            'gateway'         => 'paypal',
            'environment'     => $mode,
            'created_at'      => current_time('timestamp', true),
            'updated_at'      => current_time('timestamp', true),
            'form_id'         => (int) $payload['form_id'],
        ];
        $this->payment_meta->saveMeta((int) $payload['submission_id'], $meta);

        return [
            'order_id'        => $order_id,
            'client_id'       => $this->settings->getOption('paypal_client_id'),
            'environment'     => $mode,
            'currency'        => $currency,
            'display_amount'  => $total,
            'cart'            => [
                'subtotal'  => $payload['cart']['subtotal'],
                'tax_total' => $payload['cart']['tax_total'],
                'tax_lines' => $payload['cart']['tax_lines'],
                'total'     => $payload['cart']['total'],
            ],
        ];
    }

    public function resumeSession(int $submission_id, string $cart_hash, string $amount_hash = '', array $cart = [])
    {
        $meta = $this->payment_meta->getMeta($submission_id);
        if (empty($meta['paypal']['order_id']) || ($meta['paypal']['cart_hash'] ?? '') !== $cart_hash) {
            return new WP_Error('order_missing', __('No matching PayPal order found to resume.', 'griffinforms-form-builder'));
        }
        if ($amount_hash !== '' && ($meta['paypal']['amount_hash'] ?? '') !== $amount_hash) {
            return new WP_Error('order_mismatch', __('Payment amount changed. Please review and try again.', 'griffinforms-form-builder'));
        }

        if (!$this->isConfigured()) {
            return new WP_Error('paypal_not_configured', __('PayPal is not fully configured.', 'griffinforms-form-builder'));
        }

        $order_id = sanitize_text_field($meta['paypal']['order_id']);
        $response = $this->apiRequest('GET', '/v2/checkout/orders/' . rawurlencode($order_id));
        if (is_wp_error($response)) {
            $this->log->add(
                'error',
                'submission',
                $submission_id,
                get_current_user_id(),
                'PayPal resumeOrder error: ' . $response->get_error_message(),
                'Payment Integration (paypal)'
            );
            return new WP_Error('paypal_error', $this->getUserSafeApiErrorMessage($response, __('Unable to resume PayPal checkout.', 'griffinforms-form-builder')));
        }

        $status = strtoupper((string) ($response['status'] ?? ''));
        $resumable_statuses = ['CREATED', 'SAVED', 'APPROVED', 'PAYER_ACTION_REQUIRED'];
        if (!in_array($status, $resumable_statuses, true)) {
            if (empty($cart['items'])) {
                return new WP_Error('order_not_resumable', __('This PayPal payment can no longer be resumed. Please restart payment from the form.', 'griffinforms-form-builder'));
            }

            $hashes = CartHasher::getInstance()->compute($cart, 'paypal');
            $rebuild = $this->createSession([
                'form_id'        => (int) ($meta['paypal']['form_id'] ?? 0),
                'submission_id'  => $submission_id,
                'cart'           => $cart,
                'cart_hash'      => $hashes['cart_hash'],
                'amount_hash'    => $hashes['amount_hash'],
                'gateway'        => 'paypal',
                'field_values'   => $cart['field_values'] ?? [],
                'field_types'    => $cart['field_types'] ?? [],
                'force_new'      => true,
            ]);

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

            return $rebuild;
        }

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

        return [
            'order_id'        => $order_id,
            'client_id'       => $this->settings->getOption('paypal_client_id'),
            'environment'     => $this->getMode(),
            'currency'        => strtoupper((string) ($meta['paypal']['currency'] ?? 'USD')),
            'display_amount'  => (float) ($meta['paypal']['display_amount'] ?? 0),
            'cart'            => [
                'subtotal'  => $cart['subtotal'] ?? 0,
                'tax_total' => $cart['tax_total'] ?? 0,
                'tax_lines' => $cart['tax_lines'] ?? [],
                'total'     => $cart['total'] ?? 0,
            ],
        ];
    }

    public function completeSession(int $submission_id, array $payload)
    {
        $meta = $this->payment_meta->getMeta($submission_id);
        if (empty($meta['paypal']['order_id'])) {
            return new WP_Error('order_missing', __('PayPal order not found for this submission.', 'griffinforms-form-builder'));
        }

        $order_id = sanitize_text_field($payload['order_id'] ?? '');
        if ($order_id === '' || $order_id !== ($meta['paypal']['order_id'] ?? '')) {
            return new WP_Error('order_mismatch', __('Order mismatch detected.', 'griffinforms-form-builder'));
        }

        if (!$this->isConfigured()) {
            return new WP_Error('paypal_not_configured', __('PayPal is not fully configured.', 'griffinforms-form-builder'));
        }

        $request_id = 'gf-capture-' . $submission_id . '-' . substr($order_id, -12);
        $response = $this->apiRequest('POST', '/v2/checkout/orders/' . rawurlencode($order_id) . '/capture', new \stdClass(), [
            'PayPal-Request-Id' => $request_id,
        ]);

        if (is_wp_error($response)) {
            $this->log->add(
                'error',
                'submission',
                $submission_id,
                get_current_user_id(),
                'PayPal captureOrder error: ' . $response->get_error_message(),
                'Payment Integration (paypal)'
            );
            return new WP_Error('paypal_error', $this->getUserSafeApiErrorMessage($response, __('Unable to capture the PayPal payment.', 'griffinforms-form-builder')));
        }

        $capture_status = $this->extractCaptureStatus($response);
        $capture_id = $this->extractCaptureId($response);
        $order_status = strtoupper((string) ($response['status'] ?? ''));

        $meta['paypal']['status'] = $order_status;
        $meta['paypal']['capture_id'] = $capture_id;
        $meta['paypal']['capture_status'] = $capture_status;
        $meta['paypal']['updated_at'] = current_time('timestamp', true);
        $this->payment_meta->saveMeta($submission_id, $meta);

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

        if ($capture_status === 'COMPLETED') {
            $status_manager->markPaid($submission_id, $context);
            $this->log->add(
                'info',
                'submission',
                $submission_id,
                get_current_user_id(),
                sprintf('PayPal order %s captured successfully.', $order_id),
                'Payment Integration (paypal)'
            );
            return true;
        }

        if (in_array($capture_status, ['DECLINED', 'FAILED', 'DENIED'], true) || in_array($order_status, ['VOIDED', 'FAILED'], true)) {
            $status_manager->markFailed($submission_id, $context);
        }

        return new WP_Error('paypal_capture_incomplete', __('PayPal did not confirm the payment yet.', 'griffinforms-form-builder'));
    }

    protected function isConfigured(): bool
    {
        return $this->settings->getOption('paypal_client_id', '') !== '' && $this->settings->getOption('paypal_client_secret', '') !== '';
    }

    protected function getMode(): string
    {
        $mode = sanitize_key((string) $this->settings->getOption('paypal_mode', 'sandbox'));
        return in_array($mode, ['sandbox', 'live'], true) ? $mode : 'sandbox';
    }

    protected function getApiBaseUrl(): string
    {
        return $this->getMode() === 'live'
            ? 'https://api-m.paypal.com'
            : 'https://api-m.sandbox.paypal.com';
    }

    protected function getAccessToken()
    {
        $client_id = trim((string) $this->settings->getOption('paypal_client_id', ''));
        $client_secret = trim((string) $this->settings->getOption('paypal_client_secret', ''));
        if ($client_id === '' || $client_secret === '') {
            return new WP_Error('paypal_not_configured', __('PayPal is not fully configured.', 'griffinforms-form-builder'));
        }

        $mode = $this->getMode();
        $cache_key = 'griffinforms_paypal_token_' . md5($mode . '|' . $client_id);
        $cached = get_transient($cache_key);
        if (is_array($cached) && !empty($cached['token'])) {
            return $cached['token'];
        }

        $response = wp_remote_post($this->getApiBaseUrl() . '/v1/oauth2/token', [
            'timeout' => 20,
            'headers' => [
                'Authorization' => 'Basic ' . base64_encode($client_id . ':' . $client_secret),
                'Content-Type'  => 'application/x-www-form-urlencoded',
                'Accept'        => 'application/json',
            ],
            'body' => [
                'grant_type' => 'client_credentials',
            ],
        ]);

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

        $code = (int) wp_remote_retrieve_response_code($response);
        $body = json_decode((string) wp_remote_retrieve_body($response), true);
        if ($code < 200 || $code >= 300 || empty($body['access_token'])) {
            return new WP_Error(
                'paypal_auth_failed',
                sanitize_text_field($body['error_description'] ?? $body['error'] ?? __('Unable to authenticate with PayPal.', 'griffinforms-form-builder'))
            );
        }

        $token = sanitize_text_field($body['access_token']);
        $expires_in = isset($body['expires_in']) ? max(60, ((int) $body['expires_in']) - 60) : 1800;
        set_transient($cache_key, ['token' => $token], $expires_in);

        return $token;
    }

    protected function apiRequest(string $method, string $path, $body = null, array $extra_headers = [])
    {
        $token = $this->getAccessToken();
        if (is_wp_error($token)) {
            return $token;
        }

        $headers = array_merge([
            'Authorization' => 'Bearer ' . $token,
            'Accept'        => 'application/json',
        ], $extra_headers);

        $args = [
            'method'  => strtoupper($method),
            'timeout' => 25,
            'headers' => $headers,
        ];

        if ($body !== null) {
            $headers['Content-Type'] = 'application/json';
            $args['headers'] = $headers;
            $args['body'] = wp_json_encode($body);
        }

        $response = wp_remote_request($this->getApiBaseUrl() . $path, $args);
        if (is_wp_error($response)) {
            return $response;
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        $raw_body = (string) wp_remote_retrieve_body($response);
        $decoded = $raw_body !== '' ? json_decode($raw_body, true) : [];
        if ($code < 200 || $code >= 300) {
            $message = is_array($decoded)
                ? sanitize_text_field($decoded['message'] ?? $decoded['error_description'] ?? $decoded['name'] ?? 'paypal_request_failed')
                : 'paypal_request_failed';
            return new WP_Error('paypal_request_failed', $message, ['status' => $code, 'body' => $decoded]);
        }

        return is_array($decoded) ? $decoded : [];
    }

    protected function buildOrderPayload(array $payload): array
    {
        $cart = $payload['cart'];
        $currency = strtoupper((string) ($cart['currency'] ?? $this->currency_helper->sanitizeCurrency('USD')));
        $item_total = isset($cart['subtotal']) ? (float) $cart['subtotal'] : 0;
        $tax_total = isset($cart['tax_total']) ? (float) $cart['tax_total'] : 0;
        $total = isset($cart['total']) ? (float) $cart['total'] : ($item_total + $tax_total);

        $amount = [
            'currency_code' => $currency,
            'value'         => $this->formatDecimal($total),
            'breakdown'     => [
                'item_total' => [
                    'currency_code' => $currency,
                    'value' => $this->formatDecimal($item_total),
                ],
            ],
        ];
        if ($tax_total > 0) {
            $amount['breakdown']['tax_total'] = [
                'currency_code' => $currency,
                'value' => $this->formatDecimal($tax_total),
            ];
        }

        return [
            'intent' => 'CAPTURE',
            'purchase_units' => [
                [
                    'reference_id' => 'gf_submission_' . (int) $payload['submission_id'],
                    'custom_id'    => 'gf_submission_' . (int) $payload['submission_id'],
                    'description'  => 'GriffinForms submission #' . (int) $payload['submission_id'],
                    'amount'       => $amount,
                ],
            ],
            'application_context' => [
                'shipping_preference' => 'NO_SHIPPING',
                'user_action'         => 'PAY_NOW',
            ],
        ];
    }

    protected function getUserSafeApiErrorMessage(WP_Error $error, string $fallback): string
    {
        $message = trim((string) $error->get_error_message());
        if ($message === '') {
            return $fallback;
        }

        $code = (string) $error->get_error_code();
        if ($code === 'paypal_not_configured') {
            return __('PayPal is not fully configured.', 'griffinforms-form-builder');
        }

        if ($code === 'paypal_auth_failed') {
            return __('PayPal authentication failed. Please verify your client ID, client secret, and mode.', 'griffinforms-form-builder');
        }

        if ($code === 'http_request_failed') {
            return __('Unable to reach PayPal right now. Please try again in a moment.', 'griffinforms-form-builder');
        }

        if ($code === 'paypal_request_failed') {
            return $message;
        }

        return $fallback;
    }

    protected function extractCaptureStatus(array $response): string
    {
        $captures = $response['purchase_units'][0]['payments']['captures'] ?? [];
        if (!empty($captures[0]['status'])) {
            return strtoupper((string) $captures[0]['status']);
        }

        return strtoupper((string) ($response['status'] ?? ''));
    }

    protected function extractCaptureId(array $response): string
    {
        $captures = $response['purchase_units'][0]['payments']['captures'] ?? [];
        return sanitize_text_field($captures[0]['id'] ?? '');
    }

    protected function formatDecimal(float $amount): string
    {
        return number_format($amount, 2, '.', '');
    }
}
