<?php
/**
 * cron_mark_shopify_refunds_in_orders.php
 *
 * مهمة هذا الملف فقط:
 * - يفحص طلبات Shopify.
 * - إذا الطلبية عليها Refund، يحدّث جدول orders المحلي.
 * - يغيّر حالة الطلبية محلياً إلى REFUNDED / PARTIALLY_REFUNDED حسب Shopify.
 * - يخزّن كامل تفاصيل الريفند داخل حقل JSON في نفس جدول orders.
 *
 * لا ينشئ Credit Memo.
 * لا يرحّل أي شيء إلى NetSuite.
 * هذه مرحلة أولى فقط لتثبيت حالة الريفند محلياً.
 *
 * Usage:
 * /cron_mark_shopify_refunds_in_orders.php?secret=102030&shopify_order_id=6860433195258&dry_run=1
 * /cron_mark_shopify_refunds_in_orders.php?secret=102030&date_min=2026-04-01&date_max=2026-04-30&limit=500
 * /cron_mark_shopify_refunds_in_orders.php?secret=102030
 */

declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');
date_default_timezone_set('Asia/Jerusalem');


require_once "config.php";


function require_config_constants(array $names): void
{
    $missing = [];

    foreach ($names as $name) {
        if (!defined($name) || constant($name) === '') {
            $missing[] = $name;
        }
    }

    if (!empty($missing)) {
        json_response([
            'ok' => false,
            'message' => 'Missing required constants in config.php.',
            'missing_constants' => $missing,
        ], 500);
    }
}

require_config_constants([
    'NS_INVENTORY_SYNC_SECRET',
    'SHOPIFY_STORE_DOMAIN',
    'SHOPIFY_CLIENT_ID',
    'SHOPIFY_CLIENT_SECRET',
    'SHOPIFY_API_VERSION',
    'SHOPIFY_REST_LIMIT',
    'SHOPIFY_TOKEN_CACHE_FILE',
    'SHOPIFY_TOKEN_REFRESH_BUFFER',
    'TABLE_ORDERS',
]);

/*
 * إذا كان config.php لا يعرّف اتصال mysqli، سيحاول الملف استخدام:
 * 1) $mysqli لو موجود.
 * 2) ثوابت DB_HOST / DB_USER / DB_PASS / DB_NAME لو موجودة.
 * 3) كائن EvolutionScript القديم $db لو موجود.
 */

/* =========================
   RESPONSE HELPERS
========================= */


function json_response(array $data, int $status = 200): void
{
    http_response_code($status);
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
    exit;
}

function fail_response(string $message, array $extra = [], int $status = 400): void
{
    json_response(array_merge([
        'ok' => false,
        'message' => $message,
    ], $extra), $status);
}

/* =========================
   SECURITY
========================= */

$secret = $_GET['secret'] ?? '';
if ($secret !== NS_INVENTORY_SYNC_SECRET) {
    fail_response('Invalid secret key.', [], 403);
}

/* =========================
   GENERIC HELPERS
========================= */

function normalize_datetime(?string $value): ?string
{
    if (!$value) {
        return null;
    }
    $timestamp = strtotime($value);
    if (!$timestamp) {
        return null;
    }
    return date('Y-m-d H:i:s', $timestamp);
}

function normalize_date(?string $value): ?string
{
    if (!$value) {
        return null;
    }
    $timestamp = strtotime($value);
    if (!$timestamp) {
        return null;
    }
    return date('Y-m-d', $timestamp);
}

function money_value($value): float
{
    if ($value === null || $value === '') {
        return 0.0;
    }
    return round((float)$value, 4);
}

function json_encode_safe($value): string
{
    return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

function array_get(array $row, array $path, $default = null)
{
    $current = $row;
    foreach ($path as $key) {
        if (!is_array($current) || !array_key_exists($key, $current)) {
            return $default;
        }
        $current = $current[$key];
    }
    return $current;
}

function bool_param(string $name, bool $default = false): bool
{
    if (!isset($_GET[$name])) {
        return $default;
    }
    return in_array(strtolower((string)$_GET[$name]), ['1', 'true', 'yes', 'y'], true);
}

/* =========================
   DATABASE CORE
   Same DB style as cron_check_netsuite_fulfillments.php:
   - config.php/global.php provides $db
   - use $db->query($sql)
   - use $db->fetch_array($query)
========================= */

function require_legacy_db_core(): void
{
    if (!isset($GLOBALS['db']) || !is_object($GLOBALS['db']) || !method_exists($GLOBALS['db'], 'query')) {
        fail_response('Database core not found. config.php/global.php must provide $db with query() method.', [], 500);
    }

    if (!method_exists($GLOBALS['db'], 'fetch_array')) {
        fail_response('Database core is incomplete. $db must provide fetch_array() method.', [], 500);
    }
}

function db_escape($value): string
{
    return addslashes((string)$value);
}

function db_query(string $sql)
{
    require_legacy_db_core();
    $res = $GLOBALS['db']->query($sql);

    if ($res === false) {
        throw new RuntimeException('DB query failed. SQL: ' . $sql);
    }

    return $res;
}

function db_fetch_assoc($result): ?array
{
    require_legacy_db_core();
    $row = $GLOBALS['db']->fetch_array($result);
    return is_array($row) ? $row : null;
}

function table_exists(string $table): bool
{
    $tableEsc = db_escape($table);
    $res = db_query("SHOW TABLES LIKE '{$tableEsc}'");
    return db_fetch_assoc($res) !== null;
}

function get_table_columns(string $table): array
{
    static $cache = [];
    if (isset($cache[$table])) {
        return $cache[$table];
    }

    $safeTable = str_replace('`', '', $table);
    $res = db_query("SHOW COLUMNS FROM `{$safeTable}`");
    $cols = [];

    while ($row = db_fetch_assoc($res)) {
        $cols[] = $row['Field'];
    }

    $cache[$table] = $cols;
    return $cols;
}

function has_column(string $table, string $column): bool
{
    return in_array($column, get_table_columns($table), true);
}

function ensure_orders_refund_columns(): array
{
    $table = TABLE_ORDERS;

    if (!table_exists($table)) {
        throw new RuntimeException('Table `' . $table . '` does not exist.');
    }

    $added = [];
    $definitions = [
        'is_refunded' => "TINYINT(1) NOT NULL DEFAULT 0",
        'refund_status' => "VARCHAR(50) DEFAULT NULL",
        'refunds_count' => "INT(11) NOT NULL DEFAULT 0",
        'refund_total_amount' => "DECIMAL(18,4) NOT NULL DEFAULT 0.0000",
        'refund_currency_code' => "VARCHAR(20) DEFAULT NULL",
        'shopify_refunds_json' => "LONGTEXT DEFAULT NULL",
        'shopify_refunded_at' => "DATETIME DEFAULT NULL",
        'refund_sync_date' => "DATETIME DEFAULT NULL",
        'refund_sync_error' => "LONGTEXT DEFAULT NULL",
    ];

    foreach ($definitions as $column => $definition) {
        if (!has_column($table, $column)) {
            db_query("ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition}");
            $added[] = $column;
        }
    }

    return $added;
}

/* =========================
   SHOPIFY TOKEN + REST CLIENT
========================= */

function read_shopify_token_cache(): ?array
{
    $file = (string)SHOPIFY_TOKEN_CACHE_FILE;
    if (!is_file($file)) {
        return null;
    }

    $raw = file_get_contents($file);
    if (!$raw) {
        return null;
    }

    $data = json_decode($raw, true);
    if (!is_array($data) || empty($data['access_token'])) {
        return null;
    }

    $expiresAt = (int)($data['expires_at'] ?? 0);
    if ($expiresAt > 0 && $expiresAt <= (time() + (int)SHOPIFY_TOKEN_REFRESH_BUFFER)) {
        return null;
    }

    return $data;
}

function write_shopify_token_cache(array $data): void
{
    $file = (string)SHOPIFY_TOKEN_CACHE_FILE;
    $dir = dirname($file);
    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }
    @file_put_contents($file, json_encode_safe($data));
}

function request_shopify_admin_token(): array
{
    if (!defined('SHOPIFY_CLIENT_ID') || !defined('SHOPIFY_CLIENT_SECRET') || SHOPIFY_CLIENT_ID === '' || SHOPIFY_CLIENT_SECRET === '') {
        throw new RuntimeException('SHOPIFY_CLIENT_ID / SHOPIFY_CLIENT_SECRET are missing in config.php.');
    }

    if (!defined('SHOPIFY_STORE_DOMAIN') || SHOPIFY_STORE_DOMAIN === '') {
        throw new RuntimeException('SHOPIFY_STORE_DOMAIN / SHOPIFY_STORE_DOMAIN is missing in config.php.');
    }

    $url = 'https://' . SHOPIFY_STORE_DOMAIN . '/admin/oauth/access_token';
    $payload = [
        'client_id' => (string)SHOPIFY_CLIENT_ID,
        'client_secret' => (string)SHOPIFY_CLIENT_SECRET,
        'grant_type' => 'client_credentials',
    ];

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_TIMEOUT => 60,
        CURLOPT_CONNECTTIMEOUT => 20,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            'Accept: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode_safe($payload),
    ]);

    $raw = curl_exec($ch);
    if ($raw === false) {
        $error = curl_error($ch);
        $errno = curl_errno($ch);
        curl_close($ch);
        throw new RuntimeException('Shopify token cURL error #' . $errno . ': ' . $error);
    }

    $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $decoded = json_decode($raw, true);
    if ($httpCode < 200 || $httpCode >= 300 || !is_array($decoded) || empty($decoded['access_token'])) {
        throw new RuntimeException('Shopify token request failed HTTP ' . $httpCode . ': ' . $raw);
    }

    $expiresIn = (int)($decoded['expires_in'] ?? 0);
    $cache = [
        'access_token' => (string)$decoded['access_token'],
        'token_type' => $decoded['token_type'] ?? 'Bearer',
        'expires_in' => $expiresIn,
        'expires_at' => $expiresIn > 0 ? time() + $expiresIn : 0,
        'created_at' => date('Y-m-d H:i:s'),
    ];

    write_shopify_token_cache($cache);
    return $cache;
}

function get_shopify_admin_access_token(): string
{
    $cached = read_shopify_token_cache();
    if ($cached && !empty($cached['access_token'])) {
        return (string)$cached['access_token'];
    }

    try {
        $fresh = request_shopify_admin_token();
        return (string)$fresh['access_token'];
    } catch (Throwable $e) {
        if (defined('SHOPIFY_ADMIN_TOKEN') && SHOPIFY_ADMIN_TOKEN !== '' && strpos((string)SHOPIFY_ADMIN_TOKEN, 'xxxxx') === false) {
            return (string)SHOPIFY_ADMIN_TOKEN;
        }
        throw $e;
    }
}

function shopify_rest_get(string $path, array $query = []): array
{
    $baseUrl = 'https://' . SHOPIFY_STORE_DOMAIN . '/admin/api/' . SHOPIFY_API_VERSION . $path;
    if (!empty($query)) {
        $baseUrl .= '?' . http_build_query($query);
    }

    $accessToken = get_shopify_admin_access_token();
    $allRows = [];
    $singleRow = null;
    $nextUrl = $baseUrl;
    $pages = 0;

    while ($nextUrl) {
        $pages++;
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $nextUrl,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => true,
            CURLOPT_TIMEOUT => 90,
            CURLOPT_CONNECTTIMEOUT => 20,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Accept: application/json',
                'X-Shopify-Access-Token: ' . $accessToken,
            ],
        ]);

        $raw = curl_exec($ch);
        if ($raw === false) {
            $error = curl_error($ch);
            $errno = curl_errno($ch);
            curl_close($ch);
            throw new RuntimeException('Shopify cURL error #' . $errno . ': ' . $error);
        }

        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $rawHeaders = substr($raw, 0, $headerSize);
        $body = substr($raw, $headerSize);
        curl_close($ch);

        if ($httpCode < 200 || $httpCode >= 300) {
            throw new RuntimeException('Shopify API HTTP ' . $httpCode . ': ' . $body);
        }

        $decoded = json_decode($body, true);
        if (!is_array($decoded)) {
            throw new RuntimeException('Invalid JSON response from Shopify: ' . $body);
        }

        foreach ($decoded as $key => $value) {
            if (is_array($value)) {
                if (array_is_list($value)) {
                    $allRows = array_merge($allRows, $value);
                } else {
                    $singleRow = $value;
                }
                break;
            }
        }

        $nextUrl = extract_next_link($rawHeaders);
        if ($pages > 100) {
            throw new RuntimeException('Pagination safety limit reached.');
        }
    }

    return [
        'rows' => $allRows,
        'row' => $singleRow,
        'pages' => $pages,
    ];
}

function extract_next_link(string $rawHeaders): ?string
{
    $lines = preg_split("/\r\n|\n|\r/", $rawHeaders);
    foreach ($lines as $line) {
        if (stripos($line, 'Link:') !== 0) {
            continue;
        }
        $parts = explode(',', trim(substr($line, 5)));
        foreach ($parts as $part) {
            if (strpos($part, 'rel="next"') === false) {
                continue;
            }
            if (preg_match('/<([^>]+)>/', $part, $matches)) {
                return $matches[1];
            }
        }
    }
    return null;
}

function fetch_shopify_order(string $shopifyOrderId): ?array
{
    $res = shopify_rest_get('/orders/' . rawurlencode($shopifyOrderId) . '.json', [
        'fields' => 'id,admin_graphql_api_id,name,order_number,created_at,updated_at,processed_at,financial_status,fulfillment_status,currency,total_price,total_refunds,total_outstanding,total_tax,total_discounts,email,phone',
    ]);
    return $res['row'];
}

function fetch_shopify_refunds_for_order(string $shopifyOrderId): array
{
    return shopify_rest_get('/orders/' . rawurlencode($shopifyOrderId) . '/refunds.json', [
        'limit' => SHOPIFY_REST_LIMIT,
    ]);
}

/* =========================
   REFUND SUMMARY
========================= */

function get_refund_money_amount(array $refund): float
{
    $total = 0.0;
    foreach (($refund['transactions'] ?? []) as $trx) {
        $kind = strtolower((string)($trx['kind'] ?? ''));
        $status = strtolower((string)($trx['status'] ?? ''));
        if ($kind !== 'refund') {
            continue;
        }
        if ($status !== '' && !in_array($status, ['success', 'pending'], true)) {
            continue;
        }
        $total += abs(money_value($trx['amount'] ?? 0));
    }
    return round($total, 4);
}

function get_refund_items_amount(array $refund): float
{
    $total = 0.0;
    foreach (($refund['refund_line_items'] ?? []) as $line) {
        $subtotal = money_value($line['subtotal'] ?? 0);
        $tax = money_value($line['total_tax'] ?? 0);
        if ($subtotal > 0 || $tax > 0) {
            $total += abs($subtotal) + abs($tax);
            continue;
        }

        $qty = money_value($line['quantity'] ?? 0);
        $price = money_value($line['line_item']['price'] ?? 0);
        $total += abs($qty * $price);
    }
    return round($total, 4);
}

function get_refund_shipping_amount(array $refund): float
{
    $total = 0.0;
    foreach (($refund['order_adjustments'] ?? []) as $adj) {
        $kind = strtolower((string)($adj['kind'] ?? ''));
        if ($kind === 'shipping_refund' || $kind === 'shipping') {
            $total += abs(money_value($adj['amount'] ?? 0)) + abs(money_value($adj['tax_amount'] ?? 0));
        }
    }
    return round($total, 4);
}

function summarize_refunds(array $refunds, ?array $shopifyOrder): array
{
    $refundIds = [];
    $refundGids = [];
    $lastRefundAt = null;
    $itemsAmount = 0.0;
    $shippingAmount = 0.0;
    $moneyAmount = 0.0;
    $currency = $shopifyOrder['currency'] ?? null;

    foreach ($refunds as $refund) {
        if (!empty($refund['id'])) {
            $refundIds[] = (string)$refund['id'];
        }
        if (!empty($refund['admin_graphql_api_id'])) {
            $refundGids[] = (string)$refund['admin_graphql_api_id'];
        }

        $createdAt = normalize_datetime($refund['created_at'] ?? null);
        if ($createdAt && (!$lastRefundAt || strtotime($createdAt) > strtotime($lastRefundAt))) {
            $lastRefundAt = $createdAt;
        }

        $itemsAmount += get_refund_items_amount($refund);
        $shippingAmount += get_refund_shipping_amount($refund);
        $moneyAmount += get_refund_money_amount($refund);

        if (!$currency) {
            $currency = $refund['currency'] ?? ($refund['transactions'][0]['currency'] ?? null);
        }
    }

    $totalAmount = $moneyAmount > 0 ? $moneyAmount : ($itemsAmount + $shippingAmount);

    $shopifyFinancialStatus = strtoupper((string)($shopifyOrder['financial_status'] ?? ''));
    if ($shopifyFinancialStatus === '') {
        $shopifyFinancialStatus = $totalAmount > 0 ? 'REFUNDED' : 'REFUND_EXISTS';
    }

    return [
        'is_refunded' => count($refunds) > 0 ? 1 : 0,
        'refund_status' => $shopifyFinancialStatus,
        'refunds_count' => count($refunds),
        'refund_total_amount' => round($totalAmount, 4),
        'refund_items_amount' => round($itemsAmount, 4),
        'refund_shipping_amount' => round($shippingAmount, 4),
        'refund_money_amount' => round($moneyAmount, 4),
        'refund_currency_code' => $currency,
        'shopify_refunded_at' => $lastRefundAt,
        'refund_ids' => $refundIds,
        'refund_gids' => $refundGids,
    ];
}

/* =========================
   ORDERS TABLE OPERATIONS
========================= */

function get_local_orders_to_scan(?string $specificShopifyOrderId, ?string $dateMin, ?string $dateMax, int $limit): array
{
    $table = TABLE_ORDERS;
    $columns = get_table_columns($table);

    if (!in_array('shopify_order_id', $columns, true)) {
        throw new RuntimeException('orders table must have `shopify_order_id` column.');
    }

    $where = ["`shopify_order_id` IS NOT NULL", "`shopify_order_id` <> ''"];

    if ($specificShopifyOrderId) {
        $sid = db_escape($specificShopifyOrderId);
        $where[] = "`shopify_order_id` = '{$sid}'";
    }

    $dateColumn = null;
    foreach (['updated_at_shopify', 'updated_at', 'created_at_shopify', 'created_at', 'order_date'] as $candidate) {
        if (in_array($candidate, $columns, true)) {
            $dateColumn = $candidate;
            break;
        }
    }

    if (!$specificShopifyOrderId && $dateColumn && $dateMin) {
        $where[] = "DATE(`{$dateColumn}`) >= '" . db_escape($dateMin) . "'";
    }

    if (!$specificShopifyOrderId && $dateColumn && $dateMax) {
        $where[] = "DATE(`{$dateColumn}`) <= '" . db_escape($dateMax) . "'";
    }

    $limit = max(1, min($limit, 2000));
    $sql = "SELECT * FROM `{$table}` WHERE " . implode(' AND ', $where) . " ORDER BY `id` DESC LIMIT {$limit}";
    $res = db_query($sql);

    $rows = [];
    while ($row = db_fetch_assoc($res)) {
        $rows[] = $row;
    }

    return $rows;
}

function update_local_order_refund_info(string $shopifyOrderId, ?array $shopifyOrder, array $refunds, array $summary, bool $dryRun): array
{
    $table = TABLE_ORDERS;

    $refundJsonPayload = [
        'shopify_order' => $shopifyOrder,
        'summary' => $summary,
        'refunds' => $refunds,
        'synced_at' => date('Y-m-d H:i:s'),
    ];

    $financialStatus = $summary['refund_status'];
    $refundStatus = $summary['refund_status'];
    $refundsCount = (int)$summary['refunds_count'];
    $refundTotal = (float)$summary['refund_total_amount'];
    $currency = $summary['refund_currency_code'];
    $refundedAt = $summary['shopify_refunded_at'];
    $refundJson = json_encode_safe($refundJsonPayload);

    if ($dryRun) {
        return [
            'dry_run' => true,
            'would_update' => [
                'financial_status' => $financialStatus,
                'is_refunded' => 1,
                'refund_status' => $refundStatus,
                'refunds_count' => $refundsCount,
                'refund_total_amount' => $refundTotal,
                'refund_currency_code' => $currency,
                'shopify_refunded_at' => $refundedAt,
                'shopify_refunds_json_preview' => $refundJsonPayload,
            ],
        ];
    }

    $assignments = [
        "`financial_status` = '" . db_escape($financialStatus) . "'",
        "`is_refunded` = 1",
        "`refund_status` = '" . db_escape($refundStatus) . "'",
        "`refunds_count` = {$refundsCount}",
        "`refund_total_amount` = " . number_format($refundTotal, 4, '.', ''),
        "`refund_currency_code` = " . ($currency ? "'" . db_escape($currency) . "'" : "NULL"),
        "`shopify_refunds_json` = '" . db_escape($refundJson) . "'",
        "`shopify_refunded_at` = " . ($refundedAt ? "'" . db_escape($refundedAt) . "'" : "NULL"),
        "`refund_sync_date` = NOW()",
        "`refund_sync_error` = NULL",
    ];

    if ($shopifyOrder) {
        if (!empty($shopifyOrder['updated_at'])) {
            $updatedAt = normalize_datetime($shopifyOrder['updated_at']);
            if ($updatedAt && has_column($table, 'updated_at_shopify')) {
                $assignments[] = "`updated_at_shopify` = '" . db_escape($updatedAt) . "'";
            }
        }
        if (!empty($shopifyOrder['fulfillment_status']) && has_column($table, 'fulfillment_status')) {
            $assignments[] = "`fulfillment_status` = '" . db_escape(strtoupper((string)$shopifyOrder['fulfillment_status'])) . "'";
        }
    }

    if (has_column($table, 'updated_at')) {
        $assignments[] = "`updated_at` = NOW()";
    }

    $sid = db_escape($shopifyOrderId);
    $sql = "UPDATE `{$table}` SET " . implode(', ', $assignments) . " WHERE `shopify_order_id` = '{$sid}' LIMIT 1";
    db_query($sql);

    return [
        'dry_run' => false,
        'updated' => true,
        'financial_status' => $financialStatus,
        'refund_status' => $refundStatus,
        'refunds_count' => $refundsCount,
        'refund_total_amount' => $refundTotal,
        'refund_currency_code' => $currency,
        'shopify_refunded_at' => $refundedAt,
    ];
}

function mark_order_refund_error(string $shopifyOrderId, string $error): void
{
    $table = TABLE_ORDERS;
    if (!has_column($table, 'refund_sync_error')) {
        return;
    }
    $sid = db_escape($shopifyOrderId);
    $err = db_escape($error);
    db_query("UPDATE `{$table}` SET `refund_sync_error` = '{$err}', `refund_sync_date` = NOW() WHERE `shopify_order_id` = '{$sid}' LIMIT 1");
}

/* =========================
   MAIN
========================= */

require_legacy_db_core();

try {
    $dryRun = bool_param('dry_run', false);
    $specificShopifyOrderId = isset($_GET['shopify_order_id']) ? trim((string)$_GET['shopify_order_id']) : null;
    $dateMin = isset($_GET['date_min']) ? normalize_date((string)$_GET['date_min']) : null;
    $dateMax = isset($_GET['date_max']) ? normalize_date((string)$_GET['date_max']) : null;
    $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 500;

    $addedColumns = [];
    if (!$dryRun || bool_param('alter_in_dry_run', true)) {
        $addedColumns = ensure_orders_refund_columns();
    }

    $orders = get_local_orders_to_scan($specificShopifyOrderId, $dateMin, $dateMax, $limit);

    $result = [
        'ok' => true,
        'message' => $dryRun ? 'Dry run completed. No local orders were updated.' : 'Shopify refunds were checked and local orders were updated.',
        'mode' => 'mark_refunds_in_orders_only',
        'dry_run' => $dryRun,
        'filters' => [
            'shopify_order_id' => $specificShopifyOrderId,
            'date_min' => $dateMin,
            'date_max' => $dateMax,
            'limit' => $limit,
        ],
        'table' => TABLE_ORDERS,
        'added_columns' => $addedColumns,
        'orders_scanned' => count($orders),
        'orders_with_refunds' => 0,
        'orders_updated' => 0,
        'orders_without_refunds' => 0,
        'errors_count' => 0,
        'items' => [],
    ];

    foreach ($orders as $localOrder) {
        $shopifyOrderId = (string)($localOrder['shopify_order_id'] ?? '');
        if ($shopifyOrderId === '') {
            continue;
        }

        try {
            $shopifyOrder = null;
            try {
                $shopifyOrder = fetch_shopify_order($shopifyOrderId);
            } catch (Throwable $e) {
                // لا نوقف العملية إذا جلب الطلب نفسه فشل؛ نقدر نكمل من refunds endpoint.
                $shopifyOrder = null;
            }

            $refundFetch = fetch_shopify_refunds_for_order($shopifyOrderId);
            $refunds = $refundFetch['rows'];

            if (empty($refunds)) {
                $result['orders_without_refunds']++;
                $result['items'][] = [
                    'ok' => true,
                    'status' => 'no_refund',
                    'shopify_order_id' => $shopifyOrderId,
                    'message' => 'No refunds found for this Shopify order.',
                ];
                continue;
            }

            $summary = summarize_refunds($refunds, $shopifyOrder);
            $update = update_local_order_refund_info($shopifyOrderId, $shopifyOrder, $refunds, $summary, $dryRun);

            $result['orders_with_refunds']++;
            if (!$dryRun) {
                $result['orders_updated']++;
            }

            $result['items'][] = [
                'ok' => true,
                'status' => $dryRun ? 'dry_run' : 'updated',
                'shopify_order_id' => $shopifyOrderId,
                'order_name' => $shopifyOrder['name'] ?? ($localOrder['order_name'] ?? null),
                'summary' => $summary,
                'update' => $update,
            ];
        } catch (Throwable $e) {
            $result['errors_count']++;
            if (!$dryRun) {
                mark_order_refund_error($shopifyOrderId, $e->getMessage());
            }
            $result['items'][] = [
                'ok' => false,
                'status' => 'error',
                'shopify_order_id' => $shopifyOrderId,
                'message' => $e->getMessage(),
            ];
        }
    }

    json_response($result);
} catch (Throwable $e) {
    fail_response('Refund local order update failed.', [
        'error' => $e->getMessage(),
    ], 500);
}
