<?php
/**
 * Shopify ecommerce adapter
 * Included by api.php — expects $db, $pm, $action, $input to be set.
 * detect_packie_status() is defined in courier/packie.php which is included before this file.
*/

if (!class_exists('Logger')) require_once __DIR__ . '/../logger.php';

// ── Adapter transport — Shopify-specific HTTP and GraphQL ─────────────────────
if (!function_exists('ecommerce_http_call')) {
    function ecommerce_http_call(string $method, string $endpoint, array $data = []): ?array {
        global $pm;
        $config  = $pm->getEcomConfig();
        $url_raw = $config['store_url'] ?? '';
        if (!$url_raw) return null;
        $host    = preg_replace('#^https?://#', '', rtrim($url_raw, '/'));
        $version = $config['api_version'] ?? '2024-01';
        $url     = 'https://' . $host . '/admin/api/' . $version . '/' . $endpoint;
        $token   = $config['api_key'] ?? '';
        $curl    = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HTTPHEADER, ['X-Shopify-Access-Token: ' . $token, 'Content-Type: application/json']);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        if ($method === 'POST' || $method === 'PUT') {
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
        }
        $response = curl_exec($curl);
        $curl_err = curl_error($curl);
        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        if ($curl_err || $http_code >= 400) {
            Logger::error('shopify_http', 'API call failed', [
                'method' => $method, 'endpoint' => $endpoint,
                'http_code' => $http_code, 'curl_error' => $curl_err,
            ]);
        }
        curl_close($curl);
        return json_decode($response, true);
    }
}

if (!function_exists('ecommerce_graphql_call')) {
    function ecommerce_graphql_call(string $query, array $variables = []): ?array {
        global $pm;
        $config  = $pm->getEcomConfig();
        $url_raw = $config['store_url'] ?? '';
        if (!$url_raw) return null;
        $host    = preg_replace('#^https?://#', '', rtrim($url_raw, '/'));
        $version = $config['api_version'] ?? '2024-01';
        $url     = 'https://' . $host . '/admin/api/' . $version . '/graphql.json';
        $token   = $config['api_key'] ?? '';
        $curl    = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HTTPHEADER, ['X-Shopify-Access-Token: ' . $token, 'Content-Type: application/json']);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode(['query' => $query, 'variables' => $variables]));
        $response = curl_exec($curl);
        $curl_err = curl_error($curl);
        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        if ($curl_err || $http_code >= 400) {
            Logger::error('shopify_graphql', 'GraphQL call failed', [
                'http_code' => $http_code, 'curl_error' => $curl_err,
            ]);
        }
        curl_close($curl);
        return json_decode($response, true);
    }
}

if (!function_exists('ecommerce_sync_note')) {
    function ecommerce_sync_note(string $order_id, string $note_text): array {
        $get = ecom_call('GET', "orders/{$order_id}.json?fields=tags");
        if (!is_array($get) || !isset($get['order'])) {
            return ['success' => false, 'error' => 'Shopify order fetch failed'];
        }

        $tags  = explode(',', (string)($get['order']['tags'] ?? ''));
        $clean = [];
        foreach ($tags as $t) {
            if (trim($t) && stripos(trim($t), 'Note:') !== 0) $clean[] = trim($t);
        }
        if ($note_text !== '') $clean[] = "Note: {$note_text}";

        $put = ecom_call('PUT', "orders/{$order_id}.json", ['order' => ['id' => $order_id, 'tags' => implode(',', $clean)]]);
        return ['success' => is_array($put), 'error' => is_array($put) ? null : 'Shopify update failed'];
    }
}

// ── ecommerce_get_order_meta — fetch minimal order data (name + tags) ────────
if (!function_exists('ecommerce_get_order_meta')) {
    function ecommerce_get_order_meta(string $order_id): ?array {
        $res = ecom_call('GET', "orders/{$order_id}.json?fields=id,name,tags");
        if (!is_array($res) || !isset($res['order'])) return null;
        return $res['order'];
    }
}

// ── ecommerce_update_order_tags — overwrite order tags ───────────────────────
if (!function_exists('ecommerce_update_order_tags')) {
    function ecommerce_update_order_tags(string $order_id, string $tags): bool {
        $res = ecom_call('PUT', "orders/{$order_id}.json", ['order' => ['id' => $order_id, 'tags' => $tags]]);
        return is_array($res);
    }
}

// ── ecommerce_get_order_address — fetch shipping address + contact ────────────
if (!function_exists('ecommerce_get_order_address')) {
    function ecommerce_get_order_address(string $order_id): ?array {
        $res = ecom_call('GET', "orders/{$order_id}.json?fields=id,name,tags,email,phone,shipping_address");
        if (!is_array($res) || !isset($res['order'])) return null;
        return $res['order'];
    }
}

// ── ecommerce_update_order_address — update shipping address + clear risk tags
if (!function_exists('ecommerce_update_order_address')) {
    function ecommerce_update_order_address(string $order_id, string $current_tags, array $addr): bool {
        // Strip address-related tags before writing the new clean address
        $tag_list  = array_map('trim', explode(',', $current_tags));
        $risk_tags = ['check address', 'risk warning', 'rural', 'rural delivery', 'rd', 'rd address'];
        $clean     = array_filter($tag_list, fn($t) => $t !== '' && !in_array(strtolower($t), $risk_tags));
        $shipping_address = [
            'address1' => $addr['address1'] ?? '',
            'address2' => $addr['address2'] ?? '',
            'city'     => $addr['city']     ?? '',
            'zip'      => $addr['zip']      ?? '',
        ];
        if (array_key_exists('name', $addr)) $shipping_address['name'] = $addr['name'];
        if (array_key_exists('company', $addr)) $shipping_address['company'] = $addr['company'];
        if (array_key_exists('phone', $addr)) $shipping_address['phone'] = $addr['phone'];

        $order_payload = [
            'id'               => $order_id,
            'tags'             => implode(', ', $clean),
            'shipping_address' => $shipping_address,
        ];
        if (!empty($addr['email'])) $order_payload['email'] = $addr['email'];

        $res = ecom_call('PUT', "orders/{$order_id}.json", [
            'order' => $order_payload,
        ]);
        return is_array($res);
    }
}

// ── ecommerce_get_open_orders_raw — minimal order list for shipping sync ──────
if (!function_exists('ecommerce_get_open_orders_raw')) {
    function ecommerce_get_open_orders_raw(): array {
        $res = ecom_call('GET', 'orders.json?status=open&fulfillment_status=unfulfilled&limit=50');
        return $res['orders'] ?? [];
    }
}

// ── get_orders — fetch open unfulfilled Shopify orders with Packie status ─────
if (!function_exists('ecommerce_get_orders')) {
function ecommerce_get_orders(): array {
    global $db, $pm, $_boot_company_slug;
    $res        = ecom_call('GET', 'orders.json?status=open&fulfillment_status=unfulfilled&limit=50');
    $raw_orders = $res['orders'] ?? [];

    $packie_data = [];
    $res_cache   = $db->query("SELECT shopify_order_number, packie_id, packie_status, original_shopify_address, corrected_address FROM pp_packie_cache");
    while ($row = $res_cache->fetch_assoc()) {
        $packie_data[$row['shopify_order_number']] = [
            'packie_id' => $row['packie_id'],
            'status'    => $row['packie_status'],
            'original'  => $row['original_shopify_address'],
            'corrected' => $row['corrected_address'] ?? '',
        ];
    }

    $local_notes = [];
    $res_notes = $db->query("SELECT order_id, note_text FROM pp_order_notes");
    if ($res_notes) {
        while ($row = $res_notes->fetch_assoc()) {
            $local_notes[(string)$row['order_id']] = (string)$row['note_text'];
        }
    }

    $needs_import = false;
    $has_pending  = false;
    $product_ids  = [];
    $variant_ids  = [];

    foreach ($raw_orders as $o) {
        $shopify_num = str_replace('#', '', $o['name']);
        if (!isset($packie_data[$shopify_num])) {
            $needs_import = true;
        } elseif ($packie_data[$shopify_num]['status'] === 'PENDING') {
            $has_pending = true;
        }
        foreach ($o['line_items'] as $item) {
            if (!empty($item['product_id'])) $product_ids[] = $item['product_id'];
            if (!empty($item['variant_id'])) $variant_ids[] = (int)$item['variant_id'];
        }
    }

    // Only trigger background carrier sync if there's actual work to do:
    // new orders needing Packie import, or cached orders still awaiting RD validation.
    // Rate-limited to one spawn per 30 seconds per company to prevent process storms.
    if ($needs_import || $has_pending) {
        $sync_lock = '/tmp/afrihair_bg_sync_' . md5($_boot_company_slug ?? 'default') . '.lock';
        $last_sync = @filemtime($sync_lock);
        if (!$last_sync || (time() - $last_sync) >= 30) {
            @touch($sync_lock);
            $cmd = $needs_import ? 'cron_sync' : 'cron_sync skip_import';
            $company_arg = escapeshellarg($_boot_company_slug ?? '');
            $php_bin = function_exists('get_cli_php_binary') ? get_cli_php_binary() : ((defined('PHP_BINARY') && PHP_BINARY) ? PHP_BINARY : 'php');
            $api_script = dirname(__DIR__) . '/api.php';
            $cmd_line = escapeshellarg($php_bin) . ' ' . escapeshellarg($api_script) . " --company=$company_arg $cmd";
            Logger::info('ecommerce_get_orders', 'bg_exec', ['cmd' => $cmd_line]);
            $bg_spawn_output = [];
            $bg_spawn_code = 0;
            exec($cmd_line . ' > /dev/null 2>&1 &', $bg_spawn_output, $bg_spawn_code);
            Logger::info('ecommerce_get_orders', 'bg_spawn_exit_code', ['exit_code' => $bg_spawn_code]);
        }
    }

    // Build variant image map AND inventory quantities via Shopify GraphQL
    $variant_image_map        = [];
    $product_fallback_image_map = [];
    $variant_inventory_map    = []; // Maps variant_id → inventory quantity

    if (!empty($variant_ids)) {
        foreach (array_chunk(array_unique($variant_ids), 100) as $chunk) {
            $gids = array_map(fn($id) => "gid://shopify/ProductVariant/$id", $chunk);
            $query = <<<'GQL'
query VariantDetails($ids: [ID!]!) {
  nodes(ids: $ids) {
    ... on ProductVariant {
      id
      image { url }
      product { featuredImage { url } }
      inventoryQuantity
    }
  }
}
GQL;
            $gql_res = ecom_graphql($query, ['ids' => $gids]);
            foreach (($gql_res['data']['nodes'] ?? []) as $node) {
                if (!$node || empty($node['id'])) continue;
                if (preg_match('#ProductVariant/(\d+)$#', $node['id'], $m)) {
                    $variant_id = (string)$m[1];
                    $variant_image_map[$variant_id] =
                        $node['image']['url']
                        ?? $node['product']['featuredImage']['url']
                        ?? null;
                    $variant_inventory_map[$variant_id] =
                        $node['inventoryQuantity'] ?? null;
                }
            }
        }
    }

    if (!empty($product_ids)) {
        $prod_res = ecom_call('GET', 'products.json?ids=' . implode(',', array_slice(array_unique($product_ids), 0, 100)) . '&fields=id,images');
        if (isset($prod_res['products'])) {
            foreach ($prod_res['products'] as $p) {
                $product_fallback_image_map[(string)$p['id']] = $p['images'][0]['src'] ?? null;
            }
        }
    }

    // Instant status for orders not yet in cache
    $insert_stmt = $db->prepare("
        INSERT INTO pp_packie_cache (shopify_order_number, packie_id, packie_status, original_shopify_address)
        VALUES (?, ?, ?, ?)
        ON DUPLICATE KEY UPDATE
            packie_id     = VALUES(packie_id),
            packie_status = VALUES(packie_status),
            original_shopify_address = IF(original_shopify_address IS NULL OR original_shopify_address = '', VALUES(original_shopify_address), original_shopify_address)
    ");

    $output = [];

    foreach ($raw_orders as $o) {
        $num = str_replace('#', '', $o['name']);

        if (!isset($packie_data[$num])) {
            $sa       = $o['shipping_address'] ?? [];
            $street   = trim((string)($sa['address1']    ?? ''));
            $suburb   = trim((string)($sa['address2']    ?? ''));
            $city     = trim((string)($sa['city']        ?? ''));
            $postcode = trim((string)($sa['zip']         ?? ''));
            $country  = trim((string)($sa['country_code'] ?? ($sa['country'] ?? 'NZ')));

            $original_address = implode(', ', array_filter([$street, $suburb, $city, $postcode]));
            $instant_status   = detect_packie_status($street, $suburb, $city, $postcode, $country, $num);

            $packie_data[$num] = ['packie_id' => 0, 'status' => $instant_status, 'original' => $original_address];

            // packie_id is an INT NOT NULL column — insert 0 rather than an empty string
            $blank = 0;
            $insert_stmt->bind_param("siss", $num, $blank, $instant_status, $original_address);
            $insert_stmt->execute();
        }

        $p_info = $packie_data[$num] ?? ['status' => 'PENDING', 'original' => ''];
        if ($p_info['status'] === 'PRINTED') continue;

        $skip      = false;
        $note      = '';
        $risky     = false;
        $addr_check = false;

        foreach (explode(',', $o['tags'] ?? '') as $t) {
            $ct = strtolower(trim($t));
            if ($ct === 'risk warning')            $risky = true;
            if ($ct === 'check address')           $addr_check = true;
            if ($ct === 'packed' || stripos($ct, 'packed by') === 0) $skip = true;
        }
        if ($skip) continue;

        $note = $local_notes[(string)($o['id'] ?? '')] ?? '';

        $items = [];
        foreach ($o['line_items'] as $i) {
            $variant_id  = (string)($i['variant_id'] ?? '');
            $product_id  = (string)($i['product_id'] ?? '');
            $variant_img =
                ($variant_id && !empty($variant_image_map[$variant_id]) ? $variant_image_map[$variant_id] : null)
                ?? ($product_id && !empty($product_fallback_image_map[$product_id]) ? $product_fallback_image_map[$product_id] : null)
                ?? 'https://placehold.co/100x100/png?text=No+Img';

            // Get stock level from Shopify inventory (fetched via GraphQL above)
            $stock_level = null;
            if ($variant_id && isset($variant_inventory_map[$variant_id])) {
                $stock_level = (int)$variant_inventory_map[$variant_id];
            }

            $items[] = [
                'name' => $i['name'],
                'qty'  => $i['quantity'],
                'sku'  => $i['sku'] ?: 'No SKU',
                'img'  => $variant_img,
                'stock_level' => $stock_level,
                'price' => (float)($i['price'] ?? 0),
                'weight' => max(0.001, ((float)($i['grams'] ?? 0)) / 1000),
                'hsCode' => $i['harmonized_system_code'] ?? null,
                'countryOfOrigin' => $i['country_code_of_origin'] ?? null,
            ];
        }

        $method   = $o['shipping_lines'][0]['title'] ?? 'Standard';
        $order_created_at = $o['created_at'] ?? ($o['processed_at'] ?? '');
        $output[] = [
            'id'               => (string)$o['id'],
            'name'             => $o['name'],
            'order_created_at'  => $order_created_at,
            'order_created_display' => function_exists('afri_short_order_datetime') ? afri_short_order_datetime($order_created_at) : '',
            'customer'         => $o['shipping_address']['name'] ?? 'Customer',
            'receiver_company' => $o['shipping_address']['company'] ?? '',
            'receiver_building' => '',
            'email'            => $o['email'] ?? '',
            'phone'            => $o['shipping_address']['phone'] ?? $o['phone'] ?? '',
            'address'          => trim(($o['shipping_address']['address1'] ?? '') . ', ' . ($o['shipping_address']['city'] ?? '')),
            'postcode'         => $o['shipping_address']['zip'] ?? '',
            'method'           => $method,
            'country'          => $o['shipping_address']['country_code'] ?? ($o['shipping_address']['country'] ?? ''),
            'is_pickup'        => (stripos($method, 'Pickup') !== false || stripos($method, '247 Cuba') !== false),
            'unpaid'           => ($o['financial_status'] !== 'paid'),
            'note'             => $note,
            'items'            => $items,
            'packie_status'    => $p_info['status'],
            'original_address' => $p_info['original'],
            'corrected_address' => $p_info['corrected'] ?? '',
            'is_risky'         => $risky,
            'needs_address_check' => $addr_check,
            'source'           => 'ecommerce',
            'scanner_allowed'  => true,
        ];
    }

    return $output;
}
}

