<?php
/**
 * Jobs.php
 *
 * @since      1.0.0
 * @package    POPToFattureInCloud
 * @author     alfiopiccione <alfio.piccione@gmail.com>
 * @license    http://opensource.org/licenses/gpl-2.0.php GPL v2
 *
 * Copyright (C) 2025 POP
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

namespace WooPoPToFattureInCloud;

use function WooPoPToFattureInCloud\Functions\createInvoice;
use function WooPoPToFattureInCloud\Functions\getActiveApi;
use function WooPoPToFattureInCloud\Functions\refreshOAuthToken;
use function WooPoPToFattureInCloud\Functions\sendInvoice;
use function WooPoPToFattureInCloud\Functions\verifyEInvoice;

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

/**
 * Class Jobs
 *
 * @author alfiopiccione <alfio.piccione@gmail.com>
 */
class Jobs
{
    /**
     * Api connected
     *
     * @var mixed
     */
    public static $connected;

    /**
     * Scheduled events option
     *
     * @var
     */
    private static $scheduledEventsOption;

    /**
     * Scheduled events time option
     *
     * @var
     */
    private static $scheduledEventsTimeOption;

    /**
     * Nonce
     *
     * @var false|string
     */
    private static $nonce;

    /**
     * Jobs constructor.
     */
    public function __construct()
    {
        self::$connected                 = getActiveApi();
        self::$scheduledEventsOption     = get_option('wfc_scheduled_events');
        self::$scheduledEventsTimeOption = get_option('wfc_scheduled_events_time') ?: 12;
        self::$nonce                     = wp_create_nonce('wfc_scripts-ajax_nonce');
    }

    /**
     * Job timing
     *
     * @return false|float|int
     */
    public static function jobTiming()
    {
        $devTime  = time() + MINUTE_IN_SECONDS + mt_rand(1, 10);
        $prodTime = time() + HOUR_IN_SECONDS * (int)self::$scheduledEventsTimeOption + mt_rand(1, 10);

        $prodTime = apply_filters('wfc-job_timing', $prodTime);

        return 'dev' === WP_FATT_CLOUD ? $devTime : $prodTime;
    }

    /**
     * Determine whether an order status has already reached a configured trigger.
     *
     * Known WooCommerce statuses are compared by progression so recovery jobs can
     * still run after the order advances beyond the original trigger.
     *
     * @param string $currentStatus
     * @param string $triggerStatus
     *
     * @return bool
     */
    public static function hasReachedAutomationStatus(string $currentStatus, string $triggerStatus): bool
    {
        $currentStatus = sanitize_key($currentStatus);
        $triggerStatus = sanitize_key($triggerStatus);

        if ('' === $currentStatus || '' === $triggerStatus) {
            return false;
        }

        if ($currentStatus === $triggerStatus) {
            return true;
        }

        $progression = array(
            'pending'    => 10,
            'on-hold'    => 20,
            'processing' => 30,
            'completed'  => 40,
        );

        if (! isset($progression[$currentStatus], $progression[$triggerStatus])) {
            return false;
        }

        return $progression[$currentStatus] >= $progression[$triggerStatus];
    }

    /**
     * Expand a trigger status to the statuses that should still be recoverable.
     *
     * @param string $triggerStatus
     *
     * @return array
     */
    public static function expandRecoveryStatuses(string $triggerStatus): array
    {
        $triggerStatus = sanitize_key($triggerStatus);

        $orderedStatuses = array('pending', 'on-hold', 'processing', 'completed');
        $startIndex      = array_search($triggerStatus, $orderedStatuses, true);

        if (false === $startIndex) {
            return '' !== $triggerStatus ? array($triggerStatus) : array();
        }

        return array_slice($orderedStatuses, $startIndex);
    }

    /**
     * Build the statuses queried by the recovery cron based on active triggers.
     *
     * @param string|null $invoiceTrigger
     * @param string|null $receiptTrigger
     *
     * @return array
     */
    public static function buildRecoveryQueryStatuses(?string $invoiceTrigger, ?string $receiptTrigger): array
    {
        $statuses = array_merge(
            self::expandRecoveryStatuses((string)$invoiceTrigger),
            self::expandRecoveryStatuses((string)$receiptTrigger)
        );

        $statuses = array_values(array_unique(array_filter($statuses)));
        $progression = array(
            'pending'    => 10,
            'on-hold'    => 20,
            'processing' => 30,
            'completed'  => 40,
        );

        usort($statuses, static function ($left, $right) use ($progression) {
            $leftWeight  = isset($progression[$left]) ? $progression[$left] : PHP_INT_MAX;
            $rightWeight = isset($progression[$right]) ? $progression[$right] : PHP_INT_MAX;

            if ($leftWeight === $rightWeight) {
                return strcmp($left, $right);
            }

            return $leftWeight <=> $rightWeight;
        });

        return $statuses;
    }

    /**
     * Build the order query args used by the twice-daily recovery cron.
     *
     * @param string|null $invoiceTrigger
     * @param string|null $receiptTrigger
     * @param int         $modifiedAfterTimestamp
     *
     * @return array
     */
    public static function buildRecoveryOrderQueryArgs(?string $invoiceTrigger, ?string $receiptTrigger, int $modifiedAfterTimestamp): array
    {
        return array(
            'status'        => self::buildRecoveryQueryStatuses($invoiceTrigger, $receiptTrigger),
            'limit'         => -1,
            'orderby'       => 'date',
            'order'         => 'DESC',
            // Orders modified in last 7 days (works for any trigger status and
            // also catches records still missing the _invoice_sent meta row).
            'date_modified' => ">{$modifiedAfterTimestamp}",
            'return'        => 'ids',
        );
    }

    /**
     * Scheduled
     *
     * @param string $hook       Action hook to execute when the event is run.
     * @param array  $data       Optional. Array containing arguments to pass to the
     *                           hook's callback function. Each value in the array is passed
     *                           to the callback as an individual parameter. The array keys
     *                           are ignored. Default: empty array.
     * @param int    $time       Unix timestamp (UTC) for when to next run the event.
     *
     * @return bool
     */
    public static function scheduled(string $hook, array $data, int $time): bool
    {
        $scheduled = wp_schedule_single_event(
            $time,
            $hook,
            $data
        );

        $timeZone = new TimeZone();
        $timeZone = new \DateTimeZone($timeZone->getTimeZone()->getName());
        $dateTime = new \DateTime('now');
        $dateTime->setTimezone($timeZone);
        $date = $dateTime->format('d/m/Y H:i');

        if ($scheduled) {
            // Log
            \WooPoPToFattureInCloud\Functions\log("Scheduled - {$hook} \nDate: {$date} \nTimeStamp: {$time}\nEvent Data: " . wp_json_encode(array($data)) . "\n");

            return true;
        }

        \WooPoPToFattureInCloud\Functions\log("Scheduled Error - {$hook} \nDate: {$date} \nTimeStamp: {$time}\nEvent Data: " . wp_json_encode(array($data)) . "\n");

        return false;
    }

    /**
     * WooCommerce change status trigger
     *
     * @param int    $orderID The order id
     * @param string $from    The from state
     * @param string $to      The to state
     * @param string $provider
     *
     * @return bool
     * @throws \Exception
     */
    public static function changeStatus(int $orderID, string $from, string $to, string $provider = 'woocommerce'): bool
    {
        if ('on' !== self::$scheduledEventsOption && 'on' !== get_option('wfc_scheduled_receipt_events')) {
            return false;
        }

        $order = \WcElectronInvoice\Providers\OrderQuery::instance()->getProviderOrder($orderID, $provider);
        // Check WC_Order instance
        $wcOrderClass = \WooPoPToFattureInCloud\Functions\wcOrderClassName($order, '\WC_Order');

        if (! is_subclass_of($order, '\WcElectronInvoice\Providers\Order') &&
            ! $order instanceof $wcOrderClass) {
            return false;
        }

        $isReceipt   = 'receipt' === $order->get_billing_choice_type();
        $choiceType  = $order->get_billing_choice_type() ?: '(empty)';
        $orderStatus = $order->get_status();
        $paymentMethod = method_exists($order, 'get_payment_method') ? $order->get_payment_method() : '(n/a)';

        $statusNotes = array_map(static function ($note) {
            return '[' . $note->date_created->format('d/m/Y H:i:s') . '] ' . wp_strip_all_tags($note->content);
        }, wc_get_order_notes(['order_id' => $order->get_id(), 'type' => 'system']));

        \WooPoPToFattureInCloud\Functions\log(
            "changeStatus: OrderID {$orderID} | from={$from} to={$to} | status={$orderStatus} | choice_type={$choiceType} | payment={$paymentMethod}\n" .
            "Status history:\n" . implode("\n", $statusNotes) . "\n"
        );

        if ($isReceipt && 'on' !== get_option('wfc_scheduled_receipt_events')) {
            \WooPoPToFattureInCloud\Functions\log("changeStatus skipped: OrderID {$orderID} - receipt but wfc_scheduled_receipt_events is off\n");

            return false;
        }
        if (! $isReceipt && 'on' !== self::$scheduledEventsOption) {
            \WooPoPToFattureInCloud\Functions\log("changeStatus skipped: OrderID {$orderID} - not receipt but wfc_scheduled_events is off\n");

            return false;
        }

        // Return if total is 0
        if (floatval(0) === floatval($order->get_total())) {
            \WooPoPToFattureInCloud\Functions\log("changeStatus skipped: OrderID {$orderID} - order total is 0\n");

            return false;
        }

        // Check if document exist
        $documentID = $order->get_meta('wfc-issued_document_id');
        if ($documentID) {
            // Log
            \WooPoPToFattureInCloud\Functions\log("Document ID exist: {$documentID}" . "\n");

            return false;
        }

        // Check if the invoice was already sent
        $checkSent = $order->get_meta("_invoice_sent", true);
        if ('sent' === $checkSent) {
            // Log
            \WooPoPToFattureInCloud\Functions\log("Document is sent: {$checkSent}" . "\n");

            return false;
        }

        $valueOrderStatus = get_option('wfc_scheduled_order_status', 'completed');
        $receiptStatus    = get_option('wfc_scheduled_receipt_order_status', 'completed');
        $triggerStatus    = $isReceipt ? $receiptStatus : $valueOrderStatus;
        // FROM trigger or FROM equal TO or TO not trigger return false
        if ($from === $to || ! self::hasReachedAutomationStatus($to, $triggerStatus)) {
            $scheduleAllowed = (bool)apply_filters(
                'wfc-change_status_schedule_allowed',
                false,
                $order,
                $from,
                $to,
                $provider
            );
            if (! $scheduleAllowed) {
                \WooPoPToFattureInCloud\Functions\log(
                    "changeStatus skipped: OrderID {$orderID} - schedule gate blocked | from={$from} to={$to} | invoice_trigger={$valueOrderStatus} | receipt_trigger={$receiptStatus} | choice_type={$choiceType}\n"
                );
                $nextScheduled = wp_next_scheduled(
                    'WooPoPToFattureInCloudJobsScheduledImport',
                    [$order->get_id(), $provider]
                );
                if ($nextScheduled) {
                    wp_clear_scheduled_hook('WooPoPToFattureInCloudJobsScheduledImport', [$order->get_id(), $provider]);
                }

                return false;
            }
        }

        // Scheduled Import — receipts fire immediately, invoices use configured delay
        $isReceiptImmediate = (
            'receipt' === $order->get_billing_choice_type() &&
            'on' === get_option('wfc_scheduled_receipt_events')
        );
        $time          = $isReceiptImmediate ? time() + mt_rand(1, 10) : self::jobTiming();
        $nextScheduled = wp_next_scheduled(
            'WooPoPToFattureInCloudJobsScheduledImport',
            [$order->get_id(), $provider]
        );
        if (! $nextScheduled) {
            self::scheduled(
                'WooPoPToFattureInCloudJobsScheduledImport',
                [$order->get_id(), $provider],
                $time
            );
        } else {
            \WooPoPToFattureInCloud\Functions\log("Event already scheduled for order {$order->get_id()} and provider {$provider} to the " . date('Y-m-d H:i:s', $nextScheduled));
        }

        return true;
    }

    /**
     * Auto complete trigger
     *
     * @param int    $id The order id
     * @param string $provider
     *
     * @return bool
     * @throws \Exception
     */
    public static function autoCompleted(int $id, string $provider = 'woocommerce'): bool
    {
        if ('on' !== self::$scheduledEventsOption && 'on' !== get_option('wfc_scheduled_receipt_events')) {
            return false;
        }

        $order = \WcElectronInvoice\Providers\OrderQuery::instance()->getProviderOrder($id, $provider);
        // Check if order exist.
        $checkSent = \WcElectronInvoice\Functions\getPostMeta('_invoice_sent', null, $id, true, 'order', $provider);
        // Check if document exist
        $documentID = \WcElectronInvoice\Functions\getPostMeta('wfc-issued_document_id', null, $id, true, 'order',
            $provider);
        // Check WC_Order instance
        $wcOrderClass = \WooPoPToFattureInCloud\Functions\wcOrderClassName($order, '\WC_Order');

        if (! is_subclass_of($order, '\WcElectronInvoice\Providers\Order') &&
            ! $order instanceof $wcOrderClass) {
            return false;
        }

        $isReceipt     = 'receipt' === $order->get_billing_choice_type();
        $choiceType    = $order->get_billing_choice_type() ?: '(empty)';
        $orderStatus   = $order->get_status();
        $paymentMethod = method_exists($order, 'get_payment_method') ? $order->get_payment_method() : '(n/a)';

        $statusNotes = array_map(static function ($note) {
            return '[' . $note->date_created->format('d/m/Y H:i:s') . '] ' . wp_strip_all_tags($note->content);
        }, wc_get_order_notes(['order_id' => $order->get_id(), 'type' => 'system']));

        \WooPoPToFattureInCloud\Functions\log(
            "autoCompleted: OrderID {$id} | status={$orderStatus} | choice_type={$choiceType} | payment={$paymentMethod}\n" .
            "Status history:\n" . implode("\n", $statusNotes) . "\n"
        );

        if ($isReceipt && 'on' !== get_option('wfc_scheduled_receipt_events')) {
            \WooPoPToFattureInCloud\Functions\log("autoCompleted skipped: OrderID {$id} - receipt but wfc_scheduled_receipt_events is off\n");

            return false;
        }
        if (! $isReceipt && 'on' !== self::$scheduledEventsOption) {
            \WooPoPToFattureInCloud\Functions\log("autoCompleted skipped: OrderID {$id} - not receipt but wfc_scheduled_events is off\n");

            return false;
        }

        // Return if total is 0
        if (floatval(0) === floatval($order->get_total())) {
            \WooPoPToFattureInCloud\Functions\log("autoCompleted skipped: OrderID {$id} - order total is 0\n");

            return false;
        }

        // Document id exist
        if (isset($documentID) && '' !== $documentID) {
            // Log
            \WooPoPToFattureInCloud\Functions\log("Document ID exist: {$documentID}" . "\n");

            return false;
        }

        // Sent
        if ('sent' === $checkSent) {
            // Log
            \WooPoPToFattureInCloud\Functions\log("Document is sent: {$checkSent}" . "\n");

            return false;
        }

        // Check status — use the trigger configured for the order type (invoice vs receipt).
        $valueOrderStatus   = $isReceipt
            ? get_option('wfc_scheduled_receipt_order_status', 'completed')
            : get_option('wfc_scheduled_order_status', 'completed');
        $isReceiptImmediate = $isReceipt && 'on' === get_option('wfc_scheduled_receipt_events');
        if (self::hasReachedAutomationStatus($orderStatus, $valueOrderStatus)) {
            // Receipts fire immediately; invoices use the configured delay.
            $time          = $isReceiptImmediate ? time() + mt_rand(1, 10) : self::jobTiming();
            $nextScheduled = wp_next_scheduled(
                'WooPoPToFattureInCloudJobsScheduledImport',
                [$order->get_id(), $provider]
            );
            if (! $nextScheduled) {
                self::scheduled(
                    'WooPoPToFattureInCloudJobsScheduledImport',
                    [$order->get_id(), $provider],
                    $time
                );
            } else {
                \WooPoPToFattureInCloud\Functions\log("Event already scheduled for order {$order->get_id()} and provider {$provider} to the " . date('Y-m-d H:i:s', $nextScheduled));
            }

            return true;
        }

        \WooPoPToFattureInCloud\Functions\log(
            "autoCompleted skipped: OrderID {$id} - status mismatch | order_status={$orderStatus} | trigger={$valueOrderStatus} | choice_type={$choiceType}\n"
        );

        return false;
    }

    /**
     * Create Invoice
     *
     * @param $ID
     *
     * @return void
     */
    public static function createInvoice($ID, $provider = 'woocommerce')
    {
        if (self::$connected && ! self::$connected->expireAccessToken || (int)self::$connected->expireAccessToken < time()) {
            $_POST = [
                'fcCreateInvoiceJobEvent' => true,
            ];
            refreshOAuthToken();

            // Log
            \WooPoPToFattureInCloud\Functions\log("OrderID: {$ID} - refreshOAuthToken: " . print_r(self::$connected,
                    true) . "\n");
        }

        $order = \WcElectronInvoice\Providers\OrderQuery::instance()->getProviderOrder($ID, $provider);

        \WooPoPToFattureInCloud\Functions\log(
            "createInvoice (job): OrderID {$ID} | status=" . $order->get_status() .
            " | choice_type=" . ($order->get_billing_choice_type() ?: '(empty)') . "\n"
        );

        // Return if document exist
        $documentID = \WcElectronInvoice\Functions\getPostMeta('wfc-issued_document_id', null, $order->get_id(), true,
            'order', $provider);
        if ($documentID) {
            \WooPoPToFattureInCloud\Functions\log("createInvoice (job) skipped: OrderID {$ID} - document already exists: {$documentID}\n");

            return;
        }

        // Return if document is send
        $documentSend = \WcElectronInvoice\Functions\getPostMeta('wfc-issued_send_document', null, $order->get_id(),
            true, 'order', $provider);
        if ($documentSend) {
            \WooPoPToFattureInCloud\Functions\log("createInvoice (job) skipped: OrderID {$ID} - document already sent: {$documentSend}\n");

            return;
        }

        // Check scheduled running
        $nextScheduled = wp_get_scheduled_event(
            'WooPoPToFattureInCloudJobsScheduledImport',
            [$order->get_id(), $provider]
        );
        if (is_object($nextScheduled)) {
            if ($nextScheduled->timestamp &&
                1 === (int)did_action($nextScheduled->hook) &&
                doing_action($nextScheduled->hook) &&
                $nextScheduled->hook === current_action()
            ) {
                \WooPoPToFattureInCloud\Functions\log(
                    "OrderID {$ID} - Scheduled event:" . "\n" .
                    print_r($nextScheduled, true) . "\n" .
                    'METHOD: ' . __METHOD__
                );

                return;
            }
        }

        // Return if order not in the expected trigger status (invoice vs receipt use separate options)
        $isReceiptOrder   = 'receipt' === $order->get_billing_choice_type();
        $valueOrderStatus = $isReceiptOrder
            ? get_option('wfc_scheduled_receipt_order_status', 'completed')
            : get_option('wfc_scheduled_order_status', 'completed');
        $orderStatus      = $order->get_status();
        $statusAllowed    = (bool)apply_filters(
            'wfc-create_invoice_job_status_allowed',
            self::hasReachedAutomationStatus($orderStatus, $valueOrderStatus),
            $order,
            $valueOrderStatus,
            $orderStatus,
            $provider
        );
        if (! $statusAllowed) {
            \WooPoPToFattureInCloud\Functions\log(
                "createInvoice (job) skipped: OrderID {$ID} - status mismatch | order_status={$orderStatus} | trigger={$valueOrderStatus} | choice_type=" . ($order->get_billing_choice_type() ?: 'invoice') . "\n"
            );

            return;
        }

        // Return if total is 0
        $disableInvoiceNumber = \WcElectronInvoice\Functions\disableInvoiceOnOrderTotalZero($order);
        if (true === apply_filters('wfc-create-invoice_send_invoice_zero',
                $disableInvoiceNumber,
                $order->get_total()
            )) {
            return;
        }

        $orderType  = $order->get_type();
        $orderID    = $order->get_id();
        $choiceType = $order->get_billing_choice_type() ?: 'invoice';

        try {
            // Set all post parameters to be passed to simulate the AJAX request
            if (! defined('DOING_AJAX')) {
                define('DOING_AJAX', true);
            }

            // Set $_POST data for request

            // Create Invoice or Receipt
            $_POST = [
                'ajaxAction'              => 'fcImportRequest',
                'fcCreateInvoiceJobEvent' => true,
                'nonce'                   => self::$nonce,
                'choiceType'              => $choiceType,
                'orderID'                 => $orderID,
                'provider'                => $provider,
                'orderType'               => $orderType,
            ];

            // Log
            $time = date('d-m-Y H:i:s', time());
            \WooPoPToFattureInCloud\Functions\log("[{$time}] Jobs createInvoice POST data " . print_r($_POST,
                    true) . "\n");

            /**
             * Filter create invoice by order data
             */
            $create = false;
            if (true === apply_filters('wfc-create-invoice_job_filter', true, $order)) {
                createInvoice();
                $create = true;
            }

            if (! $create) {
                // Log
                $time = date('d-m-Y H:i:s', time());
                \WooPoPToFattureInCloud\Functions\log("[{$time}] Jobs not createInvoice - used filter 'wfc-create-invoice_job_filter' " . print_r($_POST,
                        true) . "\n");

                return;
            }

            // Only invoice request
            if ('invoice' === $choiceType) {
                sleep(10);

                // Get invoice document id
                $docID     = \WcElectronInvoice\Functions\getPostMeta("wfc-issued_document_id", null, $orderID, true,
                    'order', $provider);
                $receiptID = \WcElectronInvoice\Functions\getPostMeta("wfc-receipt_document_id", null,
                    $orderID, true, 'order', $provider) ?: null;

                // Verify and Send only invoice
                if ($docID && ! $receiptID) {
                    // Verify
                    $_POST = [
                        'ajaxAction'              => 'fcVerifyRequest',
                        'fcCreateInvoiceJobEvent' => true,
                        'nonce'                   => self::$nonce,
                        'docID'                   => $docID,
                        'provider'                => $provider,
                        'id'                      => $orderID,
                    ];
                    verifyEInvoice();

                    sleep(10);

                    // Get valid document
                    $validDoc = \WcElectronInvoice\Functions\getPostMeta("wfc-issued_valid_document", null, $orderID,
                        true, 'order', $provider);

                    if ($validDoc) {
                        // Disabled sending option
                        $disabledSending = get_option('wfc_disabled_sending_sdi');

                        /**
                         * Filter hook for disabled send invoice to SDI
                         */
                        if ((true === apply_filters(
                                    'wfc-disabled_auto_send_invoice',
                                    false,
                                    $order->get_status(),
                                    $order)
                            ) ||
                            'on' === $disabledSending
                        ) {
                            return;
                        }

                        // Send
                        $_POST = [
                            'ajaxAction'              => 'fcSendInvoiceRequest',
                            'fcCreateInvoiceJobEvent' => true,
                            'nonce'                   => self::$nonce,
                            'docID'                   => $docID,
                            'provider'                => $provider,
                            'id'                      => $orderID,
                        ];
                        sendInvoice();
                    } else {
                        // not valid
                        $order->update_meta('wfc-issued_valid_document', false);
                        // Save
                        $order->save();
                    }
                }
            }
        } catch (\Exception $e) {
            if (401 === $e->getCode()) {
                $dataException = array(
                    'message'   => esc_html__('Unauthorized: the Token seems to be invalid, update it!',
                        WP_FATT_CLOUD_TEXTDOMAIN),
                    'e-message' => $e->getMessage(),
                    'code'      => $e->getCode(),
                );
            } else {
                $dataException = array(
                    'message' => 'dev' === WP_FATT_CLOUD ?
                        esc_html('Exception when calling createInvoice: ' . $e->getMessage() . ' Trace: ' . $e->getTraceAsString()) :
                        esc_html('Exception when calling createInvoice:'),
                    'code'    => $e->getCode(),
                );
            }

            wp_send_json($dataException);
            die();
        }
    }

    /**
     * Twice daily Upload Invoice
     *
     * @return void
     * @throws \Exception
     */
    public static function twiceDailyCreateInvoice()
    {
        $data = getActiveApi();
        if (! $data->active) {
            wp_clear_scheduled_hook('twiceDailyWooPoPToFattureInCloudJobsScheduledUpload');
            return;
        }

        $valueCronJobTwiceDaily = get_option('wfc_scheduled_twicedaily_events');
        if ('on' === $valueCronJobTwiceDaily) {

            if (! wp_next_scheduled('twiceDailyWooPoPToFattureInCloudJobsScheduledUpload')) {
                wp_schedule_event(time(),
                    'twicedaily',
                    'twiceDailyWooPoPToFattureInCloudJobsScheduledUpload'
                );
            }

            add_action("twiceDailyWooPoPToFattureInCloudJobsScheduledUpload", function () {
                $timeZone = new TimeZone();
                $timeZone = new \DateTimeZone($timeZone->getTimeZone()->getName());
                $now      = new \DateTime('now');
                $now->setTimezone($timeZone);
                $daysAgo = $now->modify('-7 days');
                $out     = $daysAgo->getTimestamp();
                $invoiceTrigger = get_option('wfc_scheduled_order_status', 'completed');
                $receiptTrigger = 'on' === get_option('wfc_scheduled_receipt_events')
                    ? get_option('wfc_scheduled_receipt_order_status', 'completed')
                    : null;

                $args = self::buildRecoveryOrderQueryArgs($invoiceTrigger, $receiptTrigger, (int)$out);

                $orders = \WcElectronInvoice\Providers\OrderQuery::instance()->getProvidersOrders($args) ?: [];

                // Log
                $counts = count($orders);
                \WooPoPToFattureInCloud\Functions\log(
                    "Jobs twiceDailyFattureInCloudJobsScheduledUpload orders: {$counts}\n args: " .
                    print_r($args, true) . "\n"
                );

                try {
                    if (! empty($orders)) {
                        foreach ($orders as $order) {
                            $provider = $order->get_provider() ?: 'woocommerce';
                            // it's useless but I'll check
                            $checkSent = \WcElectronInvoice\Functions\getPostMeta('_invoice_sent', null,
                                $order->get_id(), true,
                                'order', $provider);
                            if ('sent' === $checkSent) {
                                continue;
                            }

                            // Continue if document exist
                            $documentID = \WcElectronInvoice\Functions\getPostMeta('wfc-issued_document_id', null,
                                $order->get_id(), true, 'order', $provider);
                            if ($documentID) {
                                continue;
                            }

                            // Continue if document is send
                            $documentSend = \WcElectronInvoice\Functions\getPostMeta('wfc-issued_send_document', null,
                                $order->get_id(), true, 'order', $provider);
                            if ($documentSend) {
                                continue;
                            }

                            self::createInvoice($order->get_id(), $provider);
                        }
                    }
                } catch (\Exception $e) {
                    // Log
                    \WooPoPToFattureInCloud\Functions\log(
                        "Jobs twiceDailyFattureInCloudJobsScheduledUpload: {$e->getCode()} - {$e->getMessage()}" . "\n"
                    );
                }
            });
        } else {
            wp_clear_scheduled_hook('twiceDailyWooPoPToFattureInCloudJobsScheduledUpload');
        }
    }
}
