<?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_Custom_Queue_Backend implements Action_Wear_Queue_Backend_Interface
{
    private $uniqueJobTypes = [
        '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
    ) {
        global $wpdb;

        $table = $wpdb->prefix . 'actionwear_jobs';

        if (in_array($jobType, $this->uniqueJobTypes, true)) {
            if ($this->hasPending($jobType, $excludeJobId)) {
                return 0;
            }
        }

        $now = current_time('mysql', true);

        $data = [
            'job_type'       => $jobType,
            'payload'        => $payload !== null ? wp_json_encode($payload) : null,
            'priority'       => $priority,
            'conflict_group' => $conflictGroup,
            'status'         => 'pending',
            'attempts'       => 0,
            'max_attempts'   => $maxAttempts,
            'available_at'   => $availableAt ?: $now,
            'created_at'     => $now,
            'updated_at'     => $now,
        ];

        $formats = ['%s', '%s', '%d', '%s', '%s', '%d', '%d', '%s', '%s', '%s'];

        $result = $wpdb->insert($table, $data, $formats);

        if ($result === false) {
            Log::write("JobQueue: Failed to insert job '{$jobType}': " . $wpdb->last_error, Log::ERROR, Log::CONTEXT_CRONJOB);
            return false;
        }

        return (int) $wpdb->insert_id;
    }

    public function hasPending(string $jobType, ?int $excludeJobId = null): bool
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $sql = "SELECT COUNT(*) FROM {$table} WHERE job_type = %s AND status IN ('pending', 'claimed')";
        $params = [$jobType];

        if ($excludeJobId) {
            $sql .= " AND id != %d";
            $params[] = $excludeJobId;
        }

        $count = (int) $wpdb->get_var($wpdb->prepare($sql, ...$params));

        return $count > 0;
    }

    public function hasRunning(string $conflictGroup): bool
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table} WHERE conflict_group = %s AND status = 'claimed'",
            $conflictGroup
        ));

        return $count > 0;
    }

    public function cancel(int $jobId): bool
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $updated = $wpdb->update(
            $table,
            ['status' => 'cancelled'],
            ['id' => $jobId, 'status' => 'pending'],
            ['%s'],
            ['%d', '%s']
        );

        return $updated !== false;
    }

    public function cancelByType(string $jobType): int
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $wpdb->query($wpdb->prepare(
            "UPDATE {$table} SET status = 'cancelled' WHERE job_type = %s AND status = 'pending'",
            $jobType
        ));

        return (int) $wpdb->rows_affected;
    }

    public function getJobs(array $filters = [], int $limit = 200): array
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $where = ['1=1'];
        $params = [];

        if (!empty($filters['status'])) {
            $statuses = (array) $filters['status'];
            $placeholders = implode(',', array_fill(0, count($statuses), '%s'));
            $where[] = "status IN ($placeholders)";
            $params = array_merge($params, $statuses);
        }

        if (!empty($filters['job_type'])) {
            $where[] = 'job_type = %s';
            $params[] = $filters['job_type'];
        }

        $whereSql = implode(' AND ', $where);
        $params[] = $limit;

        $sql = "SELECT id, job_type, payload, status, priority, conflict_group, attempts, max_attempts, available_at, claimed_at, created_at
                FROM {$table}
                WHERE {$whereSql}
                ORDER BY priority ASC, id ASC
                LIMIT %d";

        if (!empty($params)) {
            $sql = $wpdb->prepare($sql, ...$params);
        }

        return $wpdb->get_results($sql);
    }

    public function pruneCompleted(int $olderThanHours = 48): int
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $wpdb->query($wpdb->prepare(
            "DELETE FROM {$table} WHERE status IN ('completed', 'failed', 'cancelled') AND updated_at < DATE_SUB(NOW(), INTERVAL %d HOUR)",
            $olderThanHours
        ));

        return (int) $wpdb->rows_affected;
    }

    public function pushProductImport(string $sku, ?array $productData = null): int
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $exists = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table}
             WHERE job_type = 'product_import'
             AND status IN ('pending', 'claimed')
             AND JSON_EXTRACT(payload, '$.sku') = %s",
            $sku
        ));

        if ($exists > 0) {
            return 0;
        }

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

        return $this->push('product_import', $payload, 10, null, 'product_import', 10);
    }

    public function batchPushProductImport(array $skus): int
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        if (empty($skus)) {
            return 0;
        }

        $this->cancelByType('product_import');

        $claimedSkus = $this->getClaimedSkus('product_import');
        $skus = array_diff($skus, $claimedSkus);

        $skus = array_values(array_unique($skus));

        if (empty($skus)) {
            return 0;
        }

        $now = current_time('mysql', true);
        $inserted = 0;
        $chunks = array_chunk($skus, 500);

        foreach ($chunks as $chunk) {
            $values = [];
            foreach ($chunk as $sku) {
                $payload = wp_json_encode(['sku' => $sku]);
                $values[] = $wpdb->prepare(
                    "(%s, %s, %d, %s, %s, %d, %d, %s, %s, %s)",
                    'product_import',
                    $payload,
                    10,
                    'product_import',
                    'pending',
                    0,
                    10,
                    $now,
                    $now,
                    $now
                );
            }

            $sql = "INSERT INTO {$table} (job_type, payload, priority, conflict_group, status, attempts, max_attempts, available_at, created_at, updated_at) VALUES " . implode(',', $values);
            $result = $wpdb->query($sql);

            if ($result === false) {
                Log::write("JobQueue: Batch insert failed: " . $wpdb->last_error, Log::ERROR, Log::CONTEXT_CRONJOB);
            } else {
                $inserted += (int) $result;
            }
        }

        Log::write("JobQueue: Batch-inserted {$inserted} product_import jobs", Log::INFO, Log::CONTEXT_CRONJOB);
        return $inserted;
    }

    public function countPending(): int
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        return (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$table} WHERE status IN ('pending', 'claimed')"
        );
    }

    public function hasPendingJobs(): bool
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $count = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$table} WHERE status = 'pending' AND available_at <= NOW() LIMIT 1"
        );

        return $count > 0;
    }

    private function getClaimedSkus(string $jobType): array
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $results = $wpdb->get_col($wpdb->prepare(
            "SELECT JSON_UNQUOTE(JSON_EXTRACT(payload, '$.sku'))
             FROM {$table}
             WHERE job_type = %s AND status = 'claimed'",
            $jobType
        ));

        return $results ?: [];
    }

    public function getJobCounts(): array
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $rows = $wpdb->get_results(
            "SELECT job_type, status, COUNT(*) as cnt
             FROM {$table}
             WHERE status IN ('pending', 'claimed', 'failed')
             GROUP BY job_type, status"
        );

        $counts = [];
        foreach ($rows as $row) {
            if (!isset($counts[$row->job_type])) {
                $counts[$row->job_type] = ['pending' => 0, 'claimed' => 0, 'failed' => 0];
            }
            $counts[$row->job_type][$row->status] = (int) $row->cnt;
        }

        return $counts;
    }

    public function resetClaimed(): int
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $wpdb->query(
            "UPDATE {$table} SET status = 'pending', claimed_at = NULL, claimed_by = NULL
             WHERE status = 'claimed'"
        );

        return (int) $wpdb->rows_affected;
    }

    public function retryAllFailed(): int
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $wpdb->query(
            "UPDATE {$table} SET status = 'pending', attempts = 0, claimed_at = NULL, claimed_by = NULL, available_at = NOW()
             WHERE status = 'failed'"
        );

        return (int) $wpdb->rows_affected;
    }

    public function retryJob(int $jobId): bool
    {
        global $wpdb;
        $table = $wpdb->prefix . 'actionwear_jobs';

        $updated = $wpdb->query($wpdb->prepare(
            "UPDATE {$table} SET status = 'pending', attempts = 0, claimed_at = NULL, claimed_by = NULL, available_at = NOW()
             WHERE id = %d AND status = 'failed'",
            $jobId
        ));

        return $updated > 0;
    }
}
