<?php

namespace AC_SYNC\Includes\Classes\Async\Backends;

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

use AC_SYNC\Includes\Classes\Async\Action_Wear_Queue_Backend_Interface;
use AC_SYNC\Includes\Classes\Logger\Action_Wear_Log as Log;

class Action_Wear_Action_Scheduler_Backend implements Action_Wear_Queue_Backend_Interface
{
    const GROUP = 'actionwear';
    const HOOK_PREFIX = 'actionwear/';

    private $uniqueTypes = [
        'bulk_price_batch', 'bulk_quantity_batch',
        'bulk_price_fetch', 'bulk_quantity_fetch',
        'diff_price', 'diff_quantity', 'diff_catalog', 'sku_cleanup',
    ];

    public function push(
        string $jobType,
        ?array $payload = null,
        int $priority = 50,
        ?string $availableAt = null,
        ?string $conflictGroup = null,
        int $maxAttempts = 3,
        ?int $excludeJobId = null
    ) {
        $hook = self::HOOK_PREFIX . $jobType;
        $args = $payload ?? [];

        // Convert $availableAt (MySQL UTC string) to Unix timestamp
        if ($availableAt) {
            $timestamp = strtotime($availableAt . ' UTC');
            if ($timestamp === false || $timestamp <= 0) {
                $timestamp = time();
            }
        } else {
            $timestamp = time();
        }

        // For unique types: use $unique only when scheduling for NOW (seeding).
        // When scheduling for the FUTURE (self-reschedule from handler), use $unique = false
        // because the currently-running action would block its own reschedule.
        $unique = false;
        if (in_array($jobType, $this->uniqueTypes, true) && $timestamp <= time() + 60) {
            $unique = true;
        }

        $actionId = as_schedule_single_action($timestamp, $hook, [$args], self::GROUP, $unique);

        Log::write("AS push: {$jobType} scheduled at " . gmdate('Y-m-d H:i:s', $timestamp) . " UTC (unique={$unique}, id={$actionId})", Log::INFO, Log::CONTEXT_CRONJOB);

        return $actionId ?: 0;
    }

    public function pushProductImport(string $sku, ?array $productData = null): int
    {
        $hook = self::HOOK_PREFIX . 'product_import';

        // Check for existing pending/running import for this SKU
        $store = \ActionScheduler::store();
        foreach ([\ActionScheduler_Store::STATUS_PENDING, \ActionScheduler_Store::STATUS_RUNNING] as $status) {
            $existing = $store->query_actions([
                'hook'     => $hook,
                'group'    => self::GROUP,
                'status'   => $status,
                'args'     => [['sku' => $sku]],
                'per_page' => 1,
            ]);
            if (!empty($existing)) return 0;
        }

        $payload = ['sku' => $sku];
        if ($productData !== null) {
            $payload['product_data'] = $productData;
        }

        $actionId = as_schedule_single_action(time(), $hook, [$payload], self::GROUP);
        return $actionId ?: 0;
    }

    public function batchPushProductImport(array $skus): int
    {
        if (empty($skus)) return 0;

        // Cancel existing pending imports
        $this->cancelByType('product_import');

        $skus = array_values(array_unique($skus));
        $inserted = 0;
        $hook = self::HOOK_PREFIX . 'product_import';

        foreach ($skus as $sku) {
            $actionId = as_schedule_single_action(time(), $hook, [['sku' => $sku]], self::GROUP);
            if ($actionId) $inserted++;
        }

        Log::write("AS Backend: Scheduled {$inserted} product_import actions", Log::INFO, Log::CONTEXT_CRONJOB);
        return $inserted;
    }

    public function cancel(int $jobId): bool
    {
        try {
            \ActionScheduler::store()->cancel_action($jobId);
            return true;
        } catch (\Exception $e) {
            return false;
        }
    }

    public function cancelByType(string $jobType): int
    {
        $hook = self::HOOK_PREFIX . $jobType;
        as_unschedule_all_actions($hook, null, self::GROUP);
        return 1;
    }

    public function hasPending(string $jobType, ?int $excludeJobId = null): bool
    {
        $hook = self::HOOK_PREFIX . $jobType;
        return self::hasScheduledOrRunning($hook);
    }

    public function hasRunning(string $conflictGroup): bool
    {
        return false;
    }

    /**
     * Check if an action exists with pending OR running status.
     * as_has_scheduled_action() and as_next_scheduled_action() only check pending,
     * missing actions currently being executed — causing duplicate scheduling.
     */
    private static function hasScheduledOrRunning($hook)
    {
        $store = \ActionScheduler::store();

        $pending = $store->query_actions([
            'hook'     => $hook,
            'group'    => self::GROUP,
            'status'   => \ActionScheduler_Store::STATUS_PENDING,
            'per_page' => 1,
        ]);
        if (!empty($pending)) return true;

        $running = $store->query_actions([
            'hook'     => $hook,
            'group'    => self::GROUP,
            'status'   => \ActionScheduler_Store::STATUS_RUNNING,
            'per_page' => 1,
        ]);
        if (!empty($running)) return true;

        return false;
    }

    public function hasPendingJobs(): bool
    {
        $store = \ActionScheduler::store();
        $actions = $store->query_actions([
            'group'    => self::GROUP,
            'status'   => \ActionScheduler_Store::STATUS_PENDING,
            'per_page' => 1,
        ]);
        return !empty($actions);
    }

    public function countPending(): int
    {
        $store = \ActionScheduler::store();
        return count($store->query_actions([
            'group'    => self::GROUP,
            'status'   => \ActionScheduler_Store::STATUS_PENDING,
            'per_page' => -1,
        ]));
    }

    public function getJobs(array $filters = [], int $limit = 200): array
    {
        $store = \ActionScheduler::store();
        $args = [
            'group'    => self::GROUP,
            'per_page' => $limit,
            'orderby'  => 'date',
            'order'    => 'ASC',
        ];

        $statusMap = [
            'pending'   => \ActionScheduler_Store::STATUS_PENDING,
            'claimed'   => \ActionScheduler_Store::STATUS_RUNNING,
            'completed' => \ActionScheduler_Store::STATUS_COMPLETE,
            'failed'    => \ActionScheduler_Store::STATUS_FAILED,
            'cancelled' => \ActionScheduler_Store::STATUS_CANCELED,
        ];

        if (!empty($filters['status'])) {
            $statuses = (array) $filters['status'];
            if (isset($statusMap[$statuses[0]])) {
                $args['status'] = $statusMap[$statuses[0]];
            }
        }

        if (!empty($filters['job_type'])) {
            $args['hook'] = self::HOOK_PREFIX . $filters['job_type'];
        }

        $actionIds = $store->query_actions($args);
        $jobs = [];

        $statusReverseMap = array_flip($statusMap);

        foreach ($actionIds as $actionId) {
            try {
                $action = $store->fetch_action($actionId);
                $hook = $action->get_hook();
                $jobType = str_replace(self::HOOK_PREFIX, '', $hook);
                $schedule = $action->get_schedule();
                $actionArgs = $action->get_args();
                $payload = $actionArgs[0] ?? [];

                $asStatus = $store->get_status($actionId);
                $status = $statusReverseMap[$asStatus] ?? 'pending';

                $date = $schedule->get_date();
                if ($date) {
                    // AS returns UTC — convert to WP local timezone for display
                    $date->setTimezone(new \DateTimeZone(wp_timezone_string()));
                    $dateStr = $date->format('Y-m-d H:i:s');
                } else {
                    $dateStr = null;
                }

                $jobs[] = (object) [
                    'id'             => $actionId,
                    'job_type'       => $jobType,
                    'payload'        => wp_json_encode($payload),
                    'status'         => $status,
                    'priority'       => 50,
                    'conflict_group' => null,
                    'attempts'       => $status === 'failed' ? 1 : 0,
                    'max_attempts'   => 3,
                    'available_at'   => $dateStr,
                    'claimed_at'     => $status === 'claimed' ? current_time('mysql', true) : null,
                    'created_at'     => $dateStr,
                ];
            } catch (\Exception $e) {
                continue;
            }
        }

        return $jobs;
    }

    public function getJobCounts(): array
    {
        $store = \ActionScheduler::store();
        $counts = [];
        $statuses = [
            'pending' => \ActionScheduler_Store::STATUS_PENDING,
            'claimed' => \ActionScheduler_Store::STATUS_RUNNING,
            'failed'  => \ActionScheduler_Store::STATUS_FAILED,
        ];

        foreach ($statuses as $label => $asStatus) {
            $actionIds = $store->query_actions([
                'group'    => self::GROUP,
                'status'   => $asStatus,
                'per_page' => -1,
            ]);

            foreach ($actionIds as $actionId) {
                try {
                    $action = $store->fetch_action($actionId);
                    $jobType = str_replace(self::HOOK_PREFIX, '', $action->get_hook());
                    if (!isset($counts[$jobType])) {
                        $counts[$jobType] = ['pending' => 0, 'claimed' => 0, 'failed' => 0];
                    }
                    $counts[$jobType][$label]++;
                } catch (\Exception $e) {
                    continue;
                }
            }
        }

        return $counts;
    }

    public function pruneCompleted(int $olderThanHours = 48): int
    {
        // AS auto-prunes completed actions
        return 0;
    }

    public function resetClaimed(): int
    {
        // AS manages its own running actions
        return 0;
    }

    public function retryAllFailed(): int
    {
        $store = \ActionScheduler::store();
        $failedIds = $store->query_actions([
            'group'    => self::GROUP,
            'status'   => \ActionScheduler_Store::STATUS_FAILED,
            'per_page' => -1,
        ]);

        $retried = 0;
        foreach ($failedIds as $actionId) {
            try {
                $action = $store->fetch_action($actionId);
                as_schedule_single_action(time(), $action->get_hook(), $action->get_args(), self::GROUP);
                $store->cancel_action($actionId);
                $retried++;
            } catch (\Exception $e) {
                continue;
            }
        }

        return $retried;
    }

    public function retryJob(int $jobId): bool
    {
        try {
            $store = \ActionScheduler::store();
            $action = $store->fetch_action($jobId);
            as_schedule_single_action(time(), $action->get_hook(), $action->get_args(), self::GROUP);
            $store->cancel_action($jobId);
            return true;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Seed recurring jobs when AS backend is active.
     */
    public function ensureRecurringJobs(): void
    {
        $recurringJobs = [
            'diff_price'    => 7200,
            'diff_quantity' => 1800,
            'diff_catalog'  => 3600 * 36,
            'sku_cleanup'   => 21600,
        ];

        foreach ($recurringJobs as $jobType => $timing) {
            $hook = self::HOOK_PREFIX . $jobType;

            // Check if already exists before seeding
            $alreadyExists = self::hasScheduledOrRunning($hook);
            if ($alreadyExists) {
                continue;
            }

            $nextRun = time() + $timing;
            $actionId = as_schedule_single_action($nextRun, $hook, [[]], self::GROUP);

            if ($actionId) {
                Log::write("AS Backend: Seeded '{$jobType}' at " . gmdate('Y-m-d H:i:s', $nextRun) . " UTC (in {$timing}s), id={$actionId}", Log::INFO, Log::CONTEXT_CRONJOB);
            }
        }
    }
}
