<?php

namespace WcElectronInvoice;

use WcElectronInvoice\Admin\Settings\OptionPage;
use function WcElectronInvoice\Functions\disableInvoiceOnOrderTotalZero;
use function WcElectronInvoice\Functions\getXmlFromOrder;

if (! defined('ABSPATH')) {
    exit;
}

/**
 * Zoho integration via POP.
 */
class ZohoApi extends InvoiceApi
{
    /**
     * Return the normalized Zoho readiness payload for the current POP account.
     *
     * @return object
     */
    public static function getZohoPOPIntegrationData(): object
    {
        $minuteInSeconds        = defined('MINUTE_IN_SECONDS') ? (int)MINUTE_IN_SECONDS : 60;
        $integrationPluginActive = (bool)get_option('wc_el_inv-addon-zoho-via-pop', false);
        $filtered                = apply_filters('wc_el_inv-zoho_pop_integration_data', null, $integrationPluginActive);

        if (is_object($filtered) || is_array($filtered)) {
            return self::normalizeZohoIntegrationData($filtered, $integrationPluginActive);
        }

        $licenseKey = OptionPage::init()->getOptions('license_key') ?: null;
        if (! is_string($licenseKey) || '' === trim($licenseKey)) {
            return self::normalizeZohoIntegrationData([
                'reason' => 'missing_license',
            ], $integrationPluginActive);
        }

        $transientKey = self::getZohoStatusTransientKey($licenseKey);
        $cached        = get_transient($transientKey);
        if (is_object($cached) || is_array($cached)) {
            $normalizedCached = self::normalizeZohoIntegrationData($cached, $integrationPluginActive);
            if (! self::shouldRefreshZohoStatusCache($normalizedCached)) {
                return $normalizedCached;
            }
        }

        $response = wp_remote_get(
            esc_url_raw(WC_EL_INV_API_URL . 'integration/zoho/status?license_key=' . rawurlencode($licenseKey)),
            [
                'headers' => [
                    'Content-Type' => 'application/json',
                    'X-API-Key'    => $licenseKey,
                ],
                'timeout' => 5,
            ]
        );

        if (is_wp_error($response)) {
            $data = self::normalizeZohoIntegrationData([
                'reason'  => 'request_failed',
                'message' => $response->get_error_message(),
            ], $integrationPluginActive);
            set_transient($transientKey, $data, $minuteInSeconds);

            return $data;
        }

        $body          = wp_remote_retrieve_body($response);
        $decodedAssoc  = \WcElectronInvoice\Functions\safeJsonDecode($body, true);
        $decodedObject = \WcElectronInvoice\Functions\safeJsonDecode($body, false);
        $payload       = null;

        if ($decodedObject['ok'] && is_object($decodedObject['data'])) {
            $payload = $decodedObject['data'];
        } elseif ($decodedAssoc['ok'] && is_array($decodedAssoc['data'])) {
            $payload = (object)$decodedAssoc['data'];
        }

        if (! is_object($payload)) {
            $data = self::normalizeZohoIntegrationData([
                'reason'  => 'invalid_response',
                'message' => $decodedObject['error'] ?: $decodedAssoc['error'] ?: 'Invalid response',
            ], $integrationPluginActive);
            set_transient($transientKey, $data, $minuteInSeconds);

            return $data;
        }

        $data = self::normalizeZohoIntegrationData($payload, $integrationPluginActive);
        set_transient($transientKey, $data, $minuteInSeconds * 10);

        return $data;
    }

    /**
     * Invalidate cached Zoho readiness data.
     *
     * @return void
     */
    public static function clearZohoPOPIntegrationDataCache(): void
    {
        $licenseKey = OptionPage::init()->getOptions('license_key') ?: null;
        if (! is_string($licenseKey) || '' === trim($licenseKey)) {
            return;
        }

        delete_transient(self::getZohoStatusTransientKey($licenseKey));
    }

    /**
     * Cron scheduling on order status change.
     *
     * @param int    $orderID
     * @param string $from
     * @param string $to
     * @param string $provider
     *
     * @return bool
     * @throws \Exception
     */
    public static function changeStatus(int $orderID, string $from, string $to, string $provider = 'woocommerce'): bool
    {
        $zohoData = self::getZohoPOPIntegrationData();
        $level    = \WcElectronInvoice\Wizard::popGetUserLevel();
        if (! \WcElectronInvoice\Wizard::isUserLevelGte($level, 'basic') ||
            (! $zohoData->active_plugin_integration || ! $zohoData->zoho_integration)
        ) {
            return false;
        }

        $scheduledOptions = self::scheduledOptions();
        if ('yes' !== $scheduledOptions->active) {
            return false;
        }

        $order        = \WcElectronInvoice\Providers\OrderQuery::instance()->getProviderOrder($orderID, $provider);
        $wcOrderClass = \WcElectronInvoice\Functions\wcOrderClassName($order, '\WC_Order');
        if (! is_subclass_of($order, '\WcElectronInvoice\Providers\Order') &&
            ! $order instanceof $wcOrderClass) {
            return false;
        }

        if (disableInvoiceOnOrderTotalZero($order)) {
            return false;
        }

        $documentId = self::getZohoDocumentId($order, 'invoice');
        if ('' !== $documentId) {
            \WcElectronInvoice\Functions\log("Zoho changeStatus - document already exists: {$documentId}\n");

            return false;
        }

        $checkSent = $order->get_meta('_invoice_sent', true);
        if ('sent' === $checkSent) {
            \WcElectronInvoice\Functions\log("Zoho changeStatus - document is sent: {$checkSent}\n");

            return false;
        }

        $valueOrderStatus = $scheduledOptions->status;
        if ($valueOrderStatus === $from || $from === $to || $valueOrderStatus !== $to) {
            $nextScheduled = wp_next_scheduled('PoPToZohoJobsScheduledImport', [$order->get_id(), $provider]);
            if ($nextScheduled) {
                wp_clear_scheduled_hook('PoPToZohoJobsScheduledImport', [$order->get_id(), $provider]);
            }

            return false;
        }

        $time          = self::jobTiming();
        $nextScheduled = wp_next_scheduled('PoPToZohoJobsScheduledImport', [$order->get_id(), $provider]);
        if (! $nextScheduled) {
            self::scheduled('PoPToZohoJobsScheduledImport', [$order->get_id(), $provider], $time);
        } else {
            \WcElectronInvoice\Functions\log("Zoho event already scheduled for order {$order->get_id()} and provider {$provider} to the " . date('Y-m-d H:i:s', $nextScheduled));
        }

        return true;
    }

    /**
     * Cron scheduling on auto-completed orders.
     *
     * @param int    $id
     * @param string $provider
     *
     * @return bool
     */
    public static function autoCompleted(int $id, string $provider = 'woocommerce'): bool
    {
        $zohoData = self::getZohoPOPIntegrationData();
        $level    = \WcElectronInvoice\Wizard::popGetUserLevel();
        if (! \WcElectronInvoice\Wizard::isUserLevelGte($level, 'basic') ||
            (! $zohoData->active_plugin_integration || ! $zohoData->zoho_integration)
        ) {
            return false;
        }

        $scheduledOptions = self::scheduledOptions();
        if ('yes' !== $scheduledOptions->active) {
            return false;
        }

        $order        = \WcElectronInvoice\Providers\OrderQuery::instance()->getProviderOrder($id, $provider);
        $wcOrderClass = \WcElectronInvoice\Functions\wcOrderClassName($order, '\WC_Order');
        if (! is_subclass_of($order, '\WcElectronInvoice\Providers\Order') &&
            ! $order instanceof $wcOrderClass) {
            return false;
        }

        if (disableInvoiceOnOrderTotalZero($order)) {
            return false;
        }

        $documentId = self::getZohoDocumentId($order, 'invoice');
        if ('' !== $documentId) {
            \WcElectronInvoice\Functions\log("Zoho autoCompleted - document already exists: {$documentId}\n");

            return false;
        }

        $checkSent = $order->get_meta('_invoice_sent', true);
        if ('sent' === $checkSent) {
            \WcElectronInvoice\Functions\log("Zoho autoCompleted - document is sent: {$checkSent}\n");

            return false;
        }

        $valueOrderStatus = $scheduledOptions->status;
        if ($valueOrderStatus === $order->get_status()) {
            $time          = self::jobTiming();
            $nextScheduled = wp_next_scheduled('PoPToZohoJobsScheduledImport', [$order->get_id(), $provider]);
            if (! $nextScheduled) {
                self::scheduled('PoPToZohoJobsScheduledImport', [$order->get_id(), $provider], $time);
            } else {
                \WcElectronInvoice\Functions\log("Zoho event already scheduled for order {$order->get_id()} and provider {$provider} to the " . date('Y-m-d H:i:s', $nextScheduled));
            }

            return true;
        }

        return false;
    }

    /**
     * Render Zoho actions in the order UI.
     *
     * @param mixed      $output
     * @param mixed      $id
     * @param mixed      $item
     * @param string     $provider
     *
     * @return string
     */
    public static function actions($output, $id, $item = null, $provider = 'woocommerce'): string
    {
        if ($item && ! $provider) {
            $provider = isset($item['order_provider']) ? $item['order_provider'] : null;
            $provider = ! $provider && isset($item['provider']) ? $item['provider'] : $provider;
        }

        $context       = self::resolveActionDocumentContext($id, (string)$provider, is_array($item) ? $item : null);
        $orderProvider = $context['document'];
        if (! $orderProvider) {
            return $output;
        }

        $zohoData = self::getZohoPOPIntegrationData();
        if (! $zohoData->active_plugin_integration) {
            return $output;
        }

        $documentType       = $context['document_type'];
        $buttonDocumentType = $context['order_type'];
        $send               = 'credit_note' === $documentType
            ? __('Create Zoho Credit Note', WC_EL_INV_TEXTDOMAIN)
            : __('Create Zoho Invoice', WC_EL_INV_TEXTDOMAIN);
        $createdLabel       = 'credit_note' === $documentType
            ? __('Zoho credit note created', WC_EL_INV_TEXTDOMAIN)
            : __('Zoho invoice created', WC_EL_INV_TEXTDOMAIN);
        $timeoutPending     = \WcElectronInvoice\Functions\normalizeBooleanMeta(
            $orderProvider->get_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingMetaKey())
        );
        $timeoutChannel     = (string)($orderProvider->get_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingChannelMetaKey()) ?: '');
        $timeoutDocType     = (string)($orderProvider->get_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingDocumentTypeMetaKey()) ?: $documentType);
        $documentId         = self::getZohoDocumentId($orderProvider, $documentType);
        $documentNumber     = self::getZohoDocumentNumber($orderProvider, $documentType);
        $lastError          = (string)$orderProvider->get_meta(self::getZohoLastErrorMetaKey());

        $output .= '<div class="zoho_pop_actions">';
        $output .= '<h4>' . esc_html__('Zoho', WC_EL_INV_TEXTDOMAIN) . '</h4>';

        if ($timeoutPending && 'zoho' === $timeoutChannel) {
            $output .= sprintf(
                '<div style="margin:10px 0 0 0;" class="pop-notice notice-warning font-size-tiny">%s</div>',
                esc_html__('The previous Zoho send may have timed out. If Zoho created the document, confirm and save the returned document ID manually; otherwise cancel and try again.',
                    WC_EL_INV_TEXTDOMAIN)
            );
            $output .= sprintf(
                '<a href="javascript:;" data-provider="%1$s" data-id="%2$s" data-channel="zoho" data-document-type="%3$s" class="api-action cancel-timeout-pending button button-secondary" id="api_cancel_timeout_%2$s">%4$s</a>',
                esc_attr($provider),
                esc_attr((string)$id),
                esc_attr($timeoutDocType),
                esc_html__('Cancel timeout state', WC_EL_INV_TEXTDOMAIN)
            );

            return $output . '</div>';
        }

        if (! $zohoData->zoho_integration) {
            $message = ! empty($zohoData->message)
                ? $zohoData->message
                : __('Zoho is selected locally but the POP account integration is not ready.', WC_EL_INV_TEXTDOMAIN);
            $output .= sprintf(
                '<div style="margin:10px 0 0 0;" class="pop-notice notice-warning font-size-tiny">%s</div>',
                esc_html($message)
            );

            return $output . '</div>';
        }

        if ('credit_note' === $documentType) {
            $parentReference = self::getZohoParentInvoiceId($orderProvider, (string)$provider);
            if ('' === $parentReference) {
                $output .= sprintf(
                    '<div style="margin:10px 0 0 0;" class="pop-notice notice-warning font-size-tiny">%s</div>',
                    esc_html__('Zoho credit notes require a Zoho parent invoice reference on the original order. Create the Zoho invoice first.',
                        WC_EL_INV_TEXTDOMAIN)
                );

                return $output . '</div>';
            }
        }

        if ('' !== $documentId) {
            $message = $createdLabel;
            if ('' !== $documentNumber) {
                $message .= ': ' . $documentNumber;
            }
            $output .= sprintf(
                '<a href="javascript:;" disabled="disabled" class="api-action create button created" id="api_create_%1$s">%2$s</a>',
                esc_attr((string)$id),
                esc_html($message)
            );

            return $output . '</div>';
        }

        if ('' !== $lastError) {
            $output .= sprintf(
                '<div style="margin:10px 0 0 0;" class="pop-notice notice-error font-size-tiny">%s</div>',
                esc_html($lastError)
            );
        }

        $output .= sprintf(
            '<a href="javascript:;" data-provider="%1$s" data-id="%2$s" data-document-type="%3$s" class="api-action create button button-primary" id="api_create_%2$s">%4$s</a>',
            esc_attr($provider),
            esc_attr((string)$id),
            esc_attr($buttonDocumentType),
            esc_html($send)
        );

        return $output . '</div>';
    }

    /**
     * Create/send Zoho invoice or credit note.
     *
     * @return mixed|void
     * @throws \Exception
     */
    public static function createAndSendZohoInvoice()
    {
        $zohoData  = self::getZohoPOPIntegrationData();
        $level     = \WcElectronInvoice\Wizard::popGetUserLevel();
        $provider  = sanitize_text_field(\WcElectronInvoice\Functions\filterInput($_POST, 'provider') ?? '');
        $id        = sanitize_text_field(\WcElectronInvoice\Functions\filterInput($_POST, 'id') ?? '');
        $action    = \WcElectronInvoice\Functions\filterInput($_POST, 'action') ?? null;
        $method    = \WcElectronInvoice\Functions\filterInput($_POST, 'method') ?? null;

        if (! \WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob', 'feCronJobUploadRequest'])) {
            if (! check_ajax_referer('wc_el_inv_ajax-ajax_nonce', 'nonce', false)) {
                wp_send_json_error(['message' => esc_html__('Unauthorized.', WC_EL_INV_TEXTDOMAIN)]);
                return;
            }
            if (! current_user_can('manage_woocommerce')) {
                wp_send_json_error(['message' => esc_html__('Forbidden.', WC_EL_INV_TEXTDOMAIN)]);
                return;
            }
        }

        if (! \WcElectronInvoice\Integrations::isInvoiceChannelActive(\WcElectronInvoice\Integrations::CHANNEL_ZOHO)) {
            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('zoho', 'channel_mismatch');
                wp_send_json_error([
                    'message' => esc_html__('Action not allowed: another integration channel is active.', WC_EL_INV_TEXTDOMAIN),
                    'code'    => $preconditionError['code'],
                ]);
            }

            return;
        }

        if (! \WcElectronInvoice\Wizard::isUserLevelGte($level, 'basic') ||
            (! $zohoData->active_plugin_integration || ! $zohoData->zoho_integration)
        ) {
            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('zoho', 'integration_inactive');
                wp_send_json_error([
                    'message' => esc_html__('Action not allowed.', WC_EL_INV_TEXTDOMAIN),
                    'code'    => $preconditionError['code'],
                ]);
            }

            return;
        }

        if ('createAndSendZohoInvoice' !== $action) {
            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('zoho', 'action_invalid');
                wp_send_json_error([
                    'message' => esc_html__('Action not valid.', WC_EL_INV_TEXTDOMAIN),
                    'code'    => $preconditionError['code'],
                ]);
            }

            return;
        }

        $requestedType = sanitize_text_field(\WcElectronInvoice\Functions\filterInput($_POST, 'document_type') ?? '');
        $context       = self::resolveActionDocumentContext((int)$id, $provider, null, $requestedType);
        $order         = $context['document'];
        if (! $order) {
            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                wp_send_json_error(['message' => esc_html__('Order not found.', WC_EL_INV_TEXTDOMAIN)]);
            }

            return;
        }

        $type       = $context['document_type'];
        $documentId = self::getZohoDocumentId($order, $type);
        if ('' !== $documentId) {
            \WcElectronInvoice\Functions\log(
                \WcElectronInvoice\Functions\prefixNativeFlowMessage('zoho', "Document already exists: {$documentId}") . "\n"
            );

            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('zoho', 'uuid_already_exists');
                wp_send_json_error([
                    'message' => \WcElectronInvoice\Functions\prefixNativeFlowMessage('zoho', esc_html__('Document already sent.', WC_EL_INV_TEXTDOMAIN)),
                    'code'    => $preconditionError['code'],
                ]);
            }

            return;
        }

        if ('credit_note' === $type) {
            $parentZohoId = self::getZohoParentInvoiceId($order, $provider);
            if ('' === $parentZohoId) {
                self::setZohoLastError($order,
                    esc_html__('Zoho credit notes require a Zoho parent invoice reference on the original order.', WC_EL_INV_TEXTDOMAIN));

                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                    wp_send_json_error([
                        'message' => esc_html__('Zoho credit notes require a Zoho parent invoice reference on the original order.', WC_EL_INV_TEXTDOMAIN),
                        'code'    => 422,
                    ]);
                }

                return;
            }
        }

        $lockKey = \WcElectronInvoice\Functions\buildDbLockKey('zoho', 'create_and_send', (int)$id, $provider, $type);
        $lockDecision = \WcElectronInvoice\Functions\evaluateLockBusyDecision(
            \WcElectronInvoice\Functions\acquireDbLock($lockKey, 0),
            \WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob'])
        );
        if (! $lockDecision['continue']) {
            if ($lockDecision['respond_error']) {
                $lockError = \WcElectronInvoice\Functions\mapLockBusyError('zoho', 'create_and_send');
                wp_send_json_error([
                    'message' => esc_html__('Action skipped: another send/create process is already running for this order.', WC_EL_INV_TEXTDOMAIN),
                    'code'    => $lockError['code'],
                ]);
            }

            return;
        }

        try {
            $paramsFilter = function ($params) use ($type, $order, $provider) {
                $integrationMeta = \WcElectronInvoice\Functions\buildNativeCreateIntegrationMeta('zoho');
                if (! empty($integrationMeta['integration'])) {
                    $params['integration'] = $integrationMeta['integration'];
                }

                $params['environment'] = \WcElectronInvoice\Functions\getPopApiEnvironment();
                if ('credit_note' === $type) {
                    $parentZohoId = self::getZohoParentInvoiceId($order, $provider);
                    if ('' !== $parentZohoId) {
                        $params['integration']['connected_invoice_data'] = [
                            'id' => $parentZohoId,
                        ];
                    }
                }

                return $params;
            };

            add_filter('wc_el_inv-params_filter_before_api_request', $paramsFilter);

            $data             = getXmlFromOrder($order);
            $decodedJson      = \WcElectronInvoice\Functions\safeJsonDecode($data);
            $response         = $decodedJson['data'];
            $overquotaPayload = \WcElectronInvoice\Functions\extractPopApiOverquotaUiPayload($response);

            if (\WcElectronInvoice\Functions\isLikelyTimeoutError($data)) {
                \WcElectronInvoice\InvoiceApi::markNativeTimeoutPending($order, 'zoho', $type);
            }

            if (is_array($overquotaPayload)) {
                \WcElectronInvoice\Functions\persistPopApiOverquotaState($order, $overquotaPayload);
                self::setZohoLastError($order, (string)($overquotaPayload['message'] ?? ''));
                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                    wp_send_json_error($overquotaPayload, (int)($overquotaPayload['status'] ?? 500));
                }

                return;
            }

            if (! $decodedJson['ok'] || ! is_object($response)) {
                if (\WcElectronInvoice\Functions\isLikelyTimeoutError($data)) {
                    \WcElectronInvoice\InvoiceApi::markNativeTimeoutPending($order, 'zoho', $type);
                }
                self::setZohoLastError($order, esc_html__('Invalid response from Zoho sync.', WC_EL_INV_TEXTDOMAIN));
                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                    wp_send_json_error([
                        'message' => \WcElectronInvoice\Functions\prefixNativeFlowMessage('zoho', esc_html__('Invalid response from Zoho sync.', WC_EL_INV_TEXTDOMAIN)),
                        'raw'     => $data,
                        'error'   => $decodedJson['error'],
                    ]);
                }

                return;
            }

            $normalizedResponse = self::normalizeZohoSyncResponse($response);
            $responseData       = $normalizedResponse['data'];
            $success            = $normalizedResponse['success'];
            $message            = $normalizedResponse['message'];

            if (! is_object($responseData) && ! is_array($responseData)) {
                if (\WcElectronInvoice\Functions\isLikelyTimeoutError($response)) {
                    \WcElectronInvoice\InvoiceApi::markNativeTimeoutPending($order, 'zoho', $type);
                }
                self::setZohoLastError($order, esc_html__('Invalid response from Zoho sync.', WC_EL_INV_TEXTDOMAIN));
                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                    wp_send_json_error([
                        'message' => \WcElectronInvoice\Functions\prefixNativeFlowMessage('zoho', esc_html__('Invalid response from Zoho sync.', WC_EL_INV_TEXTDOMAIN)),
                        'raw'     => $response,
                    ]);
                }

                return;
            }

            $reference    = self::extractZohoDocumentReference($responseData, $type);
            $documentId   = $reference['id'];
            $documentNo   = $reference['number'];

            if (! $success) {
                $message = \WcElectronInvoice\Functions\prefixNativeFlowMessage('zoho', (string)($message ?: esc_html__('Zoho sync failed.', WC_EL_INV_TEXTDOMAIN)));
                $detail  = \WcElectronInvoice\Functions\extractNativeErrorDetail($response);
                if ('' !== $detail) {
                    $message .= ' ' . $detail;
                }
                self::setZohoLastError($order, $message);

                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                    wp_send_json_error([
                        'message' => $message,
                        'raw'     => $response,
                    ]);
                }

                return;
            }

            if (! \WcElectronInvoice\Functions\canMarkInvoiceSentWithUuid($documentId)) {
                if ('sandbox' === \WcElectronInvoice\Functions\getPopApiEnvironment()) {
                    $sandboxMessage = \WcElectronInvoice\Functions\prefixNativeFlowMessage('zoho', $message ?: esc_html__('Zoho sandbox dry run succeeded. No document was created in Zoho.', WC_EL_INV_TEXTDOMAIN));
                    if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                        wp_send_json_success([
                            'message'  => $sandboxMessage,
                            'provider' => $provider,
                            'order_id' => $id,
                            'raw'      => $response,
                        ]);
                    }

                    return;
                }

                $message = \WcElectronInvoice\Functions\prefixNativeFlowMessage('zoho', esc_html__('Zoho sync succeeded but did not return a document ID.', WC_EL_INV_TEXTDOMAIN));
                self::setZohoLastError($order, $message);
                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
                    $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('zoho', 'response_uuid_missing');
                    wp_send_json_error([
                        'message' => $message,
                        'raw'     => $response,
                        'code'    => $preconditionError['code'],
                    ]);
                }

                return;
            }

            $order->update_meta(self::getZohoDocumentIdMetaKey($type), $documentId);
            if ('' !== $documentNo) {
                $order->update_meta(self::getZohoDocumentNumberMetaKey($type), $documentNo);
            }
            $order->update_meta(self::getZohoLastStatusMetaKey(), 'created');
            $order->delete_meta(self::getZohoLastErrorMetaKey());

            self::markDocumentAsSent($order);
            \WcElectronInvoice\InvoiceApi::clearNativeTimeoutPending($order);
            \WcElectronInvoice\Functions\clearPopApiOverquotaState($order);
            $order->save();
        } finally {
            remove_filter('wc_el_inv-params_filter_before_api_request', $paramsFilter);
            \WcElectronInvoice\Functions\releaseDbLock($lockKey);
        }

        if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
            wp_send_json_success([
                'message'         => $message,
                'provider'        => $provider,
                'order_id'        => $id,
                'document_id'     => $documentId,
                'document_number' => $documentNo,
                'raw'             => $response,
            ]);

            return;
        }

        \WcElectronInvoice\Functions\log(
            $message . "\n" .
            print_r([
                'provider'        => $provider,
                'order_id'        => $id,
                'document_id'     => $documentId,
                'document_number' => $documentNo,
                'raw'             => $response,
            ], true) . "\n"
        );

        return $response;
    }

    /**
     * Normalize Zoho sync responses coming either as a standard envelope
     * (`success`, `message`, `data`) or as a flat connector payload.
     *
     * @param mixed $response
     *
     * @return array{success:bool,message:string,data:mixed}
     */
    private static function normalizeZohoSyncResponse($response): array
    {
        if (is_array($response)) {
            $response = (object)$response;
        }

        if (! is_object($response)) {
            return [
                'success' => false,
                'message' => '',
                'data'    => null,
            ];
        }

        $hasEnvelopeData = isset($response->data) && (is_object($response->data) || is_array($response->data));
        $data            = $hasEnvelopeData ? $response->data : $response;
        $success         = filter_var($response->success ?? false, FILTER_VALIDATE_BOOLEAN);
        $message         = trim((string)($response->message ?? ''));

        if ('' === $message && is_object($data)) {
            $message = trim((string)($data->message ?? ''));

            if ('' === $message && isset($data->zoho_response) && is_object($data->zoho_response)) {
                $message = trim((string)($data->zoho_response->message ?? ''));
            }
        }

        return [
            'success' => $success,
            'message' => $message,
            'data'    => $data,
        ];
    }

    /**
     * Normalize raw Zoho status payload.
     *
     * @param mixed $payload
     * @param bool  $pluginActive
     *
     * @return object
     */
    private static function normalizeZohoIntegrationData($payload, bool $pluginActive): object
    {
        if (is_array($payload)) {
            $payload = (object)$payload;
        }

        $source = $payload;
        if (is_object($payload) && isset($payload->data) && (is_object($payload->data) || is_array($payload->data))) {
            $source = is_array($payload->data) ? (object)$payload->data : $payload->data;
        }

        if (! is_object($source)) {
            $source = (object)[];
        }

        $activeConnector = trim((string)($source->active_connector ?? ''));
        $product         = trim((string)($source->zoho_product ?? ($source->product ?? '')));
        $organizationId  = trim((string)($source->zoho_org_id ?? ($source->organization_id ?? '')));
        $connected       = filter_var($source->zoho_connected ?? ($source->connected ?? false), FILTER_VALIDATE_BOOLEAN);
        $ready           = 'zoho' === strtolower($activeConnector) && $connected && '' !== $product && '' !== $organizationId;

        return (object)[
            'active_plugin_integration' => $pluginActive,
            'zoho_integration'          => $ready,
            'available'                 => ! empty($activeConnector) || $connected || '' !== $product || '' !== $organizationId,
            'connected'                 => $connected,
            'active_connector'          => $activeConnector,
            'product'                   => $product,
            'region'                    => trim((string)($source->zoho_region ?? ($source->region ?? ''))),
            'organization_id'           => $organizationId,
            'invoice_status'            => trim((string)($source->zoho_invoice_status ?? ($source->invoice_status ?? ''))),
            'create_contact_if_missing' => filter_var($source->zoho_create_contact_if_missing ?? ($source->create_contact_if_missing ?? false), FILTER_VALIDATE_BOOLEAN),
            'token_expires_at'          => isset($source->zoho_token_expires_at) ? (int)$source->zoho_token_expires_at : (isset($source->token_expires_at) ? (int)$source->token_expires_at : 0),
            'reason'                    => trim((string)($source->reason ?? ($payload->reason ?? ''))),
            'message'                   => trim((string)($source->message ?? ($payload->message ?? ''))),
        ];
    }

    /**
     * Resolve the transient key used for Zoho status caching.
     *
     * @param string $licenseKey
     *
     * @return string
     */
    private static function getZohoStatusTransientKey(string $licenseKey): string
    {
        return 'wc_el_inv_zoho_status_' . md5($licenseKey);
    }

    /**
     * Detect legacy/incomplete cached payloads that should be refreshed from POP.
     *
     * @param object $data
     *
     * @return bool
     */
    private static function shouldRefreshZohoStatusCache(object $data): bool
    {
        return 'zoho' === strtolower(trim((string)($data->active_connector ?? ''))) &&
               empty($data->reason) &&
               empty($data->message) &&
               (
                   '' === trim((string)($data->product ?? '')) ||
                   '' === trim((string)($data->organization_id ?? ''))
               );
    }

    /**
     * Extract a document id/number pair from a generic Zoho sync response.
     *
     * @param mixed  $payload
     * @param string $documentType
     *
     * @return array{id:string,number:string}
     */
    private static function extractZohoDocumentReference($payload, string $documentType): array
    {
        $documentType = strtolower(trim($documentType));
        $variants     = 'credit_note' === $documentType
            ? ['credit_note', 'credit-note', 'creditnote']
            : ['invoice'];
        $idCandidates = ['id', 'invoice_id', 'zoho_invoice_id', 'creditnote_id', 'credit_note_id', 'document_id', 'zoho_id'];
        $numberCandidates = ['number', 'invoice_number', 'zoho_invoice_number', 'creditnote_number', 'credit_note_number', 'document_number', 'display_number'];

        foreach ($variants as $variant) {
            if (is_object($payload) && isset($payload->{$variant}) && (is_object($payload->{$variant}) || is_array($payload->{$variant}))) {
                return self::extractZohoDocumentReference($payload->{$variant}, $documentType);
            }
            if (is_array($payload) && isset($payload[$variant]) && (is_object($payload[$variant]) || is_array($payload[$variant]))) {
                return self::extractZohoDocumentReference($payload[$variant], $documentType);
            }
        }

        $documentId = self::findFirstScalarValue($payload, $idCandidates);
        $number     = self::findFirstScalarValue($payload, $numberCandidates);

        return [
            'id'     => trim((string)$documentId),
            'number' => trim((string)$number),
        ];
    }

    /**
     * Recursively find the first scalar value matching any candidate key.
     *
     * @param mixed    $payload
     * @param string[] $candidateKeys
     *
     * @return string
     */
    private static function findFirstScalarValue($payload, array $candidateKeys): string
    {
        if (is_object($payload)) {
            foreach ($candidateKeys as $key) {
                if (isset($payload->{$key}) && is_scalar($payload->{$key})) {
                    $candidate = trim((string)$payload->{$key});
                    if ('' !== $candidate) {
                        return $candidate;
                    }
                }
            }

            foreach (get_object_vars($payload) as $value) {
                if (is_object($value) || is_array($value)) {
                    $resolved = self::findFirstScalarValue($value, $candidateKeys);
                    if ('' !== $resolved) {
                        return $resolved;
                    }
                }
            }
        }

        if (is_array($payload)) {
            foreach ($candidateKeys as $key) {
                if (array_key_exists($key, $payload) && is_scalar($payload[$key])) {
                    $candidate = trim((string)$payload[$key]);
                    if ('' !== $candidate) {
                        return $candidate;
                    }
                }
            }

            foreach ($payload as $value) {
                if (is_object($value) || is_array($value)) {
                    $resolved = self::findFirstScalarValue($value, $candidateKeys);
                    if ('' !== $resolved) {
                        return $resolved;
                    }
                }
            }
        }

        return '';
    }

    /**
     * Return the original-order Zoho invoice id needed to create a credit note.
     *
     * @param mixed  $document
     * @param string $provider
     *
     * @return string
     */
    private static function getZohoParentInvoiceId($document, string $provider): string
    {
        if (! is_object($document) || ! method_exists($document, 'get_parent_id')) {
            return '';
        }

        $parentId = (int)$document->get_parent_id();
        if ($parentId <= 0) {
            return '';
        }

        $parentOrder = \WcElectronInvoice\Providers\OrderQuery::instance()->getProviderOrder($parentId, $provider);
        if (! is_object($parentOrder) || ! method_exists($parentOrder, 'get_meta')) {
            return '';
        }

        return self::getZohoDocumentId($parentOrder, 'invoice');
    }

    /**
     * Persist the latest Zoho error on the targeted document.
     *
     * @param mixed  $order
     * @param string $message
     *
     * @return void
     */
    private static function setZohoLastError($order, string $message): void
    {
        if (! is_object($order) || ! method_exists($order, 'update_meta')) {
            return;
        }

        $order->update_meta(self::getZohoLastErrorMetaKey(), trim($message));
        $order->save();
    }

    /**
     * Return the Zoho document id stored on the document.
     *
     * @param mixed  $order
     * @param string $documentType
     *
     * @return string
     */
    private static function getZohoDocumentId($order, string $documentType): string
    {
        if (! is_object($order) || ! method_exists($order, 'get_meta')) {
            return '';
        }

        return trim((string)$order->get_meta(self::getZohoDocumentIdMetaKey($documentType)));
    }

    /**
     * Return the Zoho document number stored on the document.
     *
     * @param mixed  $order
     * @param string $documentType
     *
     * @return string
     */
    private static function getZohoDocumentNumber($order, string $documentType): string
    {
        if (! is_object($order) || ! method_exists($order, 'get_meta')) {
            return '';
        }

        return trim((string)$order->get_meta(self::getZohoDocumentNumberMetaKey($documentType)));
    }

    /**
     * @param string $documentType
     *
     * @return string
     */
    private static function getZohoDocumentIdMetaKey(string $documentType): string
    {
        return 'credit_note' === strtolower(trim($documentType)) ? 'zoho_pop-credit_note_id' : 'zoho_pop-invoice_id';
    }

    /**
     * @param string $documentType
     *
     * @return string
     */
    private static function getZohoDocumentNumberMetaKey(string $documentType): string
    {
        return 'credit_note' === strtolower(trim($documentType)) ? 'zoho_pop-credit_note_number' : 'zoho_pop-invoice_number';
    }

    /**
     * @return string
     */
    private static function getZohoLastStatusMetaKey(): string
    {
        return 'zoho_pop-last_status';
    }

    /**
     * @return string
     */
    private static function getZohoLastErrorMetaKey(): string
    {
        return 'zoho_pop-last_error';
    }
}
