<?php

namespace DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\Action\Process;

use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\Factory\DataProviderFactory;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\Infrastructure\Service\Listener\Items\Hookable\Hookable;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\DAO\ExportDAO;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\Entity\Export;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\Service\Logger\ExportLoggerService;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\Service\Locator\FileLocatorService;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\Infrastructure\Request\Request;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\Service\Scheduler\ExportSchedulerService;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\Service\Exporter\ProductsExporterService;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\Service\Finder\ProductsFinderService;
use InvalidArgumentException;
use Exception;
class ExportProcessAction implements Hookable
{
    public const AJAX_ACTION = 'export_products';
    public const AJAX_NONCE = 'nonce_export_products';
    public const PROCESS_EXPORT_KEY = 'dropshipping_export_process';
    private ExportDAO $export_dao;
    private ProductsExporterService $export_service;
    private Request $request;
    private ExportSchedulerService $scheduler;
    private ExportLoggerService $logger;
    private bool $finished = \false;
    private ProductsFinderService $finder;
    public function __construct(Request $request, ExportDAO $export_dao, ProductsExporterService $export_service, ExportSchedulerService $scheduler, ProductsFinderService $finder, ExportLoggerService $logger)
    {
        $this->export_dao = $export_dao;
        $this->export_service = $export_service;
        $this->request = $request;
        $this->scheduler = $scheduler;
        $this->logger = $logger;
        $this->finder = $finder;
    }
    public function hooks()
    {
        add_action('wp_ajax_' . self::AJAX_ACTION, [$this, 'process_ajax']);
    }
    public function process()
    {
        $export_entity = null;
        try {
            if ($this->export_dao->has_export_processing()) {
                $export_entity = $this->export_dao->find_processing_export();
                $this->process_export($export_entity);
            } elseif ($this->export_dao->has_next_to_export()) {
                $export_entity = $this->export_dao->find_next_to_export();
                $this->process_next_export($export_entity);
            }
        } catch (\Exception $e) {
            if (is_object($export_entity)) {
                $this->stop_export_with_error($export_entity, $e->getMessage());
            }
        }
    }
    public function process_ajax()
    {
        try {
            $uid = $this->request->get_param('post.uid')->get();
            if (empty($uid)) {
                throw new InvalidArgumentException(__('Error: uid is empty', 'dropshipping-export-free'));
            }
            $file_import = $this->export_dao->find_by_uid($uid);
            if (\in_array($file_import->get_status(), [Export::STATUS_ERROR, Export::STATUS_STOPPED])) {
                $this->reset_export_to_default($file_import);
                $this->export_dao->update($file_import);
            }
            Export::STATUS_WAITING === $file_import->get_status() ? $this->process_next_export($file_import) : $this->process_export($file_import);
            wp_send_json(['success' => \true, 'created' => $file_import->get_created(), 'total' => $file_import->get_products_count(), 'progress' => $file_import->get_formated_progress(), 'log' => $this->logger->get_formated_messages(), 'finished' => $this->finished]);
        } catch (Exception $e) {
            if (is_object($file_import)) {
                $this->stop_export_with_error($file_import, $e->getMessage());
            }
            wp_send_json(['success' => \false, 'message' => $e->getMessage()]);
        }
    }
    private function process_export(Export $export_entity)
    {
        if (\true === $this->lock_export()) {
            switch ($export_entity->get_status()) {
                case Export::STATUS_INIT:
                    $this->process_init_export($export_entity);
                    break;
                case Export::STATUS_IN_PROGRESS:
                    $this->process_export_progress($export_entity);
                    break;
            }
            $this->lock_export(\false);
        } else {
            $this->logger->notice(__('Waiting, another process is exporting now.', 'dropshipping-export-free'));
            sleep(3);
        }
    }
    private function process_init_export(Export $export_entity)
    {
        $this->logger->notice(__('Initialize export', 'dropshipping-export-free'));
        $this->logger->notice(__('Initializing is finished', 'dropshipping-export-free'));
        $export_entity->set_products_count($this->finder->count_products_to_export($export_entity));
        $export_entity->set_status(Export::STATUS_IN_PROGRESS);
        $this->export_dao->update($export_entity);
    }
    private function process_export_progress(Export $export_entity)
    {
        if (0 === $export_entity->get_last_position()) {
            $this->logger->notice(__('Export in progress', 'dropshipping-export-free'));
        }
        if (empty(get_current_user_id())) {
            $user_id = $export_entity->get_user_id();
            if (empty(\get_user_by('id', 1))) {
                $user_id = 1;
            }
            \wp_set_current_user($user_id);
        }
        $export_entity = $this->export_service->export($export_entity);
        $file_save_export = clone $export_entity;
        if ($this->export_service->is_finished()) {
            $this->finish_process($file_save_export);
        } else {
            $this->export_dao->update($export_entity);
        }
    }
    private function finish_process(Export $export_entity)
    {
        $this->logger->notice(__('Export is finished', 'dropshipping-export-free'));
        $this->reset_export_to_default($export_entity);
        $this->finished = \true;
    }
    private function reset_export_to_default(Export $file_import)
    {
        $file_import->set_next_export($this->scheduler->estimate_time($file_import->get_uid()));
        $file_import->set_last_position(0);
        $file_import->set_skipped(0);
        $file_import->set_created(0);
        $file_import->set_products_count(0);
        $file_import->set_status(Export::STATUS_WAITING);
        $this->export_dao->update($file_import);
    }
    private function process_next_export(Export $file_import)
    {
        if (\true === $this->lock_export()) {
            $this->logger->notice(__('Starting new export for uid: ', 'dropshipping-export-free') . $file_import->get_uid());
            $file_import->set_status(Export::STATUS_INIT);
            $file_import->set_start_date(time());
            $this->export_dao->update($file_import);
            $this->lock_export(\false);
        } else {
            $this->logger->notice(__('Waiting, another process is exporting now.', 'dropshipping-export-free'));
            sleep(3);
        }
    }
    private function stop_export_with_error(Export $export_entity, string $error_message = '')
    {
        $export_entity->set_status(Export::STATUS_ERROR);
        $export_entity->set_error_message($error_message);
        $this->export_dao->update($export_entity);
    }
    private function lock_export(bool $lock = \true): bool
    {
        global $wpdb;
        $uid = \uniqid();
        $time = time() + 10;
        $set_lock = function (string $key, $value) use ($wpdb): bool {
            $key = \sanitize_title($key);
            $value = \serialize($value);
            // phpcs:ignore
            $row_id = $wpdb->get_var($wpdb->prepare("SELECT option_id FROM {$wpdb->options} WHERE option_name = %s LIMIT 1", $key));
            // phpcs:ignore
            if (\is_numeric($row_id)) {
                $row = $wpdb->query($wpdb->prepare("UPDATE {$wpdb->options} SET option_value=%s WHERE option_id=%d", $value, $row_id));
                // phpcs:ignore
            } else {
                $row = $wpdb->query($wpdb->prepare("INSERT INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %s, 'no')", $key, $value));
                // phpcs:ignore
            }
            return \boolval($row);
        };
        $get_lock = function (string $key) use ($wpdb) {
            $row = $wpdb->get_var($wpdb->prepare("SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1", $key));
            // phpcs:ignore
            return unserialize($row);
            // phpcs:ignore
        };
        $has_active_lock = function ($db_data): bool {
            if (is_array($db_data)) {
                if (isset($db_data['time'])) {
                    if (intval($db_data['time']) > time()) {
                        return \true;
                    }
                }
            }
            return \false;
        };
        $is_uid_valid = function ($db_data, $uid): bool {
            if (is_array($db_data)) {
                if (isset($db_data['uid'])) {
                    if ($db_data['uid'] === $uid) {
                        return \true;
                    }
                }
            }
            return \false;
        };
        if (\false === $lock) {
            $set_lock(self::PROCESS_EXPORT_KEY, null);
            return \true;
        }
        if (!$has_active_lock($get_lock(self::PROCESS_EXPORT_KEY))) {
            $set_lock(self::PROCESS_EXPORT_KEY, ['uid' => $uid, 'time' => $time]);
            if ($is_uid_valid($get_lock(self::PROCESS_EXPORT_KEY), $uid)) {
                return \true;
            }
        }
        return \false;
    }
}
