<?php

namespace Meliconnect\Meliconnect\Core;

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

use Meliconnect\Meliconnect\Core\Helpers\Helper;
use Meliconnect\Meliconnect\Core\Models\ProcessItems;

class BatchProcessor
{
    /**
     * Entry point del proceso batch
     */
    public function run(array $config)
    {
        $lock_acquired = false;

        try {

            $this->ensureNextCron($config);

            $lock_acquired = $this->acquireLock(
                $config['lock_name'],
                $config['log_channel'],
                $config['lock_ttl'] ?? 100
            );

            if (!$lock_acquired) {
                return;
            }

            $items = ProcessItems::getPendingBatch(
                $config['process_type'],
                $config['batch_size'] ?? 10
            );

            Helper::logData('Batch size: ' . count($items), $config['log_channel']);

            if (empty($items)) {
                //Helper::logData('No items to process.', $config['log_channel']);
                return;
            }

            // marcar como processing
            $item_ids = wp_list_pluck($items, 'id');
            ProcessItems::markAsProcessing($item_ids);

            // hook opcional antes del loop
            if (!empty($config['before_loop'])) {
                call_user_func($config['before_loop'], $items);
            }

            foreach ($items as $item) {

                // cancelación opcional
                if (!empty($config['cancel_flag']) && get_option($config['cancel_flag'])) {
                    Helper::logData('Process canceled by user.', $config['log_channel']);
                    break;
                }

                $this->processSingleItem($item, $config);
            }

            // hook opcional después del loop
            if (!empty($config['after_loop'])) {
                call_user_func($config['after_loop'], $items);
            }

        } catch (\Throwable $e) {

            Helper::logData(
                'Fatal error: ' . $e->getMessage(),
                $config['log_channel']
            );

        } finally {

            if ($lock_acquired) {
                $this->releaseLock($config['lock_name']);
            }

            if (!empty($config['cancel_flag'])) {
                delete_option($config['cancel_flag']);
            }

            $this->finalizeProcess($config);
        }
    }

    /**
     * Procesa un item individual con transacción
     */
    private function processSingleItem($item, $config)
    {
        global $wpdb;

        $final_status_set = false;

        try {

            $wpdb->query('START TRANSACTION');

            Helper::logData(
                'START item ' . $item->id,
                $config['log_channel']
            );

            $result = call_user_func($config['process_item'], $item);

            if ($result === false) {
                throw new \Exception('Process returned false');
            }

            ProcessItems::markAsProcessed(
                $item->id,
                $item->process_id
            );

            $final_status_set = true;

            $wpdb->query('COMMIT');

            Helper::logData(
                'END item ' . $item->id,
                $config['log_channel']
            );

        } catch (\Throwable $e) {

            $wpdb->query('ROLLBACK');

            ProcessItems::markAsFailed(
                $item->id,
                $item->process_id,
                $e->getMessage()
            );

            $final_status_set = true;

            Helper::logData(
                'Error item ' . $item->id . ': ' . $e->getMessage(),
                $config['log_channel']
            );
        }

        // failsafe
        if (!$final_status_set) {
            ProcessItems::markAsFailed(
                $item->id,
                $item->process_id,
                'Unknown crash'
            );

            Helper::logData(
                'Failsafe triggered for item ' . $item->id,
                $config['log_channel']
            );
        }
    }

    /**
     * LOCK con TTL
     */
    private function acquireLock($lock_name, $log_channel, $ttl)
    {
        $existing_lock = get_option($lock_name);

        // limpiar lock viejo
        if ($existing_lock && (time() - $existing_lock) > $ttl) {
            delete_option($lock_name);
        }

        $lock_acquired = add_option($lock_name, time(), '', 'no');

        if (!$lock_acquired) {

            Helper::logData(
                'Lock exists: ' . json_encode(get_option($lock_name)),
                $log_channel
            );

            Helper::logData(
                'Process already running. Exit.',
                $log_channel
            );
        }

        return $lock_acquired;
    }

    private function releaseLock($lock_name)
    {
        delete_option($lock_name);
    }

    /**
     * Self-healing cron
     */
    private function ensureNextCron($config)
    {
        if (empty($config['cron_hook']) || empty($config['process_id'])) {
            return;
        }

        $timestamp = wp_next_scheduled(
            $config['cron_hook'],
            array($config['process_id'])
        );

        if (!$timestamp || $timestamp <= time()) {

            wp_schedule_single_event(
                time() + ($config['cron_delay'] ?? 30),
                $config['cron_hook'],
                array($config['process_id'])
            );

            Helper::logData(
                'Next cron scheduled.',
                $config['log_channel']
            );

        } else {

            Helper::logData(
                "Cron already scheduled at {$timestamp}",
                $config['log_channel']
            );
        }
    }

    /**
     * Finalización del proceso
     */
    private function finalizeProcess($config)
    {
        if (empty($config['process_id'])) {
            return;
        }

        $remaining = ProcessItems::countPending($config['process_id']);

        if ($remaining === 0) {

            if (!empty($config['cron_hook'])) {

                $timestamp = wp_next_scheduled(
                    $config['cron_hook'],
                    array($config['process_id'])
                );

                if ($timestamp) {
                    wp_unschedule_event(
                        $timestamp,
                        $config['cron_hook'],
                        array($config['process_id'])
                    );
                }

                Helper::logData(
                    'No more items. Cron unscheduled.',
                    $config['log_channel']
                );
            }

            Helper::logData(
                'Process finished.',
                $config['log_channel']
            );
        }
    }
}