<?php
require_once __DIR__ . '/config.php';

$limit = 10;

$mysqli = db();
$shopifyAccessToken = null;
$results = array();

$sql = "
SELECT
    om.id AS order_match_id,
    om.shopify_order_row_id,
    om.lalamove_order_row_id,
    om.share_link,
    so.shopify_order_id,
    so.shopify_name,
    so.fulfillment_status,
    lo.lalamove_order_id,
    lo.picked_up_found,
    lo.picked_up_at_text,
    lo.delivery_status,
    lo.overall_status_text
FROM order_matches om
INNER JOIN shopify_orders so ON so.id = om.shopify_order_row_id
INNER JOIN lalamove_orders lo ON lo.id = om.lalamove_order_row_id
WHERE om.match_decision IN ('auto_matched', 'manual_matched')
  AND om.tracking_sent = 0
  AND om.share_link IS NOT NULL
  AND om.share_link <> ''
  AND lo.picked_up_found = 1
ORDER BY om.matched_at ASC
LIMIT ?
";

$stmt = $mysqli->prepare($sql);
if (!$stmt) {
    json_response(array(
        'ok' => false,
        'error' => 'Failed to prepare database query.'
    ), 500);
}

$stmt->bind_param('i', $limit);
$stmt->execute();
$result = $stmt->get_result();

while ($row = $result->fetch_assoc()) {
    $shopifyOrderId = trim((string)(isset($row['shopify_order_id']) ? $row['shopify_order_id'] : ''));
    $shopifyName = trim((string)(isset($row['shopify_name']) ? $row['shopify_name'] : ''));

    if ($shopifyAccessToken === null) {
        $tokenResp = shopify_get_access_token(false);
        if (empty($tokenResp['ok'])) {
            $results[] = array(
                'shopify_order_id' => $shopifyOrderId,
                'shopify_name' => $shopifyName,
                'ok' => false,
                'step' => 'get_access_token',
                'error' => isset($tokenResp['error']) ? $tokenResp['error'] : 'Unable to get Shopify access token'
            );
            continue;
        }

        $shopifyAccessToken = $tokenResp['access_token'];
    }

    $lookupResult = shopify_find_order_for_fulfillment($shopifyAccessToken, $shopifyName, $shopifyOrderId);

    if (empty($lookupResult['ok']) || empty($lookupResult['order'])) {
        $results[] = array(
            'shopify_order_id' => $shopifyOrderId,
            'shopify_name' => $shopifyName,
            'ok' => false,
            'step' => 'find_order',
            'error' => isset($lookupResult['error']) ? $lookupResult['error'] : 'Order lookup failed'
        );
        continue;
    }

    $order = $lookupResult['order'];
    $existingFulfillments = extract_existing_fulfillments($order);
    $fulfillmentOrders = extract_fulfillment_orders($order);

    if (!empty($existingFulfillments)) {
        $results[] = array(
            'shopify_order_id' => $shopifyOrderId,
            'shopify_name' => $shopifyName,
            'ok' => true,
            'step' => 'already_fulfilled_or_started',
            'message' => 'Shopify already has fulfillment record(s). Skipping fulfill creation.',
            'existing_fulfillment_count' => count($existingFulfillments)
        );
        continue;
    }

    $lineItemsByFulfillmentOrder = build_line_items_by_fulfillment_order($fulfillmentOrders);

    if (empty($lineItemsByFulfillmentOrder)) {
        $results[] = array(
            'shopify_order_id' => $shopifyOrderId,
            'shopify_name' => $shopifyName,
            'ok' => false,
            'step' => 'no_fulfillable_items',
            'message' => 'No fulfillable fulfillment orders with remaining quantity were found.'
        );
        continue;
    }

    $mutation = "mutation fulfillmentCreate(\$fulfillment: FulfillmentInput!, \$message: String) {\n"
        . "  fulfillmentCreate(fulfillment: \$fulfillment, message: \$message) {\n"
        . "    fulfillment {\n"
        . "      id\n"
        . "      status\n"
        . "    }\n"
        . "    userErrors {\n"
        . "      field\n"
        . "      message\n"
        . "    }\n"
        . "  }\n"
        . "}";

    $variables = array(
        'fulfillment' => array(
            'notifyCustomer' => false,
            'lineItemsByFulfillmentOrder' => $lineItemsByFulfillmentOrder,
        ),
        'message' => 'Auto-fulfilled after Lalamove pickup detected by internal automation.',
    );

    $sendResp = shopify_graphql_with_token($shopifyAccessToken, $mutation, $variables);

    if ((isset($sendResp['http_status']) && ($sendResp['http_status'] === 401 || $sendResp['http_status'] === 403))
        && shopify_response_looks_like_auth_issue(isset($sendResp['raw']) ? $sendResp['raw'] : array())) {
        $refreshResp = shopify_get_access_token(true);
        if (!empty($refreshResp['ok'])) {
            $shopifyAccessToken = $refreshResp['access_token'];
            $sendResp = shopify_graphql_with_token($shopifyAccessToken, $mutation, $variables);
        }
    }

    $userErrors = array();
    if (isset($sendResp['raw']['data']['fulfillmentCreate']['userErrors'])
        && is_array($sendResp['raw']['data']['fulfillmentCreate']['userErrors'])) {
        $userErrors = $sendResp['raw']['data']['fulfillmentCreate']['userErrors'];
    }

    $hasFulfillmentId = !empty($sendResp['raw']['data']['fulfillmentCreate']['fulfillment']['id']);
    $success = !empty($sendResp['ok']) && empty($userErrors) && $hasFulfillmentId;

    if ($success) {
        $upd = $mysqli->prepare("UPDATE shopify_orders SET fulfillment_status = 'fulfilled', updated_at = NOW() WHERE id = ?");
        if ($upd) {
            $shopifyOrderRowId = (int)$row['shopify_order_row_id'];
            $upd->bind_param('i', $shopifyOrderRowId);
            $upd->execute();
            $upd->close();
        }
    }

    $results[] = array(
        'shopify_order_id' => $shopifyOrderId,
        'shopify_name' => $shopifyName,
        'ok' => $success,
        'step' => $success ? 'fulfilled' : 'fulfillment_create_failed',
        'order_gid' => isset($order['id']) ? $order['id'] : null,
        'display_fulfillment_status' => isset($order['displayFulfillmentStatus']) ? $order['displayFulfillmentStatus'] : null,
        'fulfillment_orders_count' => count($fulfillmentOrders),
        'existing_fulfillment_count' => count($existingFulfillments),
        'line_items_by_fulfillment_order' => $lineItemsByFulfillmentOrder,
        'user_errors' => $userErrors,
        'raw' => isset($sendResp['raw']) ? $sendResp['raw'] : null,
    );
}

$stmt->close();

json_response(array(
    'ok' => true,
    'count' => count($results),
    'results' => $results,
));

function shopify_find_order_for_fulfillment($accessToken, $shopifyName, $shopifyOrderId)
{
    $queries = build_shopify_lookup_candidates_for_fulfillment($shopifyName, $shopifyOrderId);
    $lastResponse = null;

    foreach ($queries as $candidate) {
        $resp = shopify_graphql_with_token($accessToken, $candidate['query'], $candidate['variables']);
        $lastResponse = $resp;

        if (empty($resp['ok'])) {
            continue;
        }

        $order = extract_order_from_lookup_response($candidate['type'], isset($resp['raw']) ? $resp['raw'] : array());
        if (!empty($order) && !empty($order['id'])) {
            return array(
                'ok' => true,
                'http_status' => isset($resp['http_status']) ? $resp['http_status'] : null,
                'order' => $order,
                'request_payload' => array(
                    'query' => $candidate['query'],
                    'variables' => $candidate['variables'],
                ),
                'response_payload' => isset($resp['raw']) ? $resp['raw'] : null,
                'lookup_method' => $candidate['method'],
            );
        }
    }

    $attemptedMethods = array();
    foreach ($queries as $candidate) {
        $attemptedMethods[] = $candidate['method'];
    }

    return array(
        'ok' => false,
        'http_status' => isset($lastResponse['http_status']) ? $lastResponse['http_status'] : null,
        'order' => null,
        'request_payload' => !empty($attemptedMethods) ? array('attempted_methods' => $attemptedMethods) : null,
        'response_payload' => isset($lastResponse['raw']) ? $lastResponse['raw'] : null,
        'lookup_method' => !empty($attemptedMethods) ? implode(',', $attemptedMethods) : null,
        'error' => 'Shopify order lookup failed.',
    );
}

function build_shopify_lookup_candidates_for_fulfillment($shopifyName, $shopifyOrderId)
{
    $orderFields = "id\n"
        . "legacyResourceId\n"
        . "name\n"
        . "displayFulfillmentStatus\n"
        . "fulfillments {\n"
        . "  id\n"
        . "  status\n"
        . "  trackingInfo {\n"
        . "    company\n"
        . "    number\n"
        . "    url\n"
        . "  }\n"
        . "}\n"
        . "fulfillmentOrders(first: 20) {\n"
        . "  nodes {\n"
        . "    id\n"
        . "    status\n"
        . "    requestStatus\n"
        . "    lineItems(first: 50) {\n"
        . "      nodes {\n"
        . "        id\n"
        . "        remainingQuantity\n"
        . "        totalQuantity\n"
        . "      }\n"
        . "    }\n"
        . "  }\n"
        . "}";

    $queries = array();

    if ($shopifyOrderId !== '' && ctype_digit($shopifyOrderId)) {
        $queries[] = array(
            'type' => 'order',
            'method' => 'order:id',
            'query' => "query FindOrderById(\$id: ID!) {\n"
                . "  order(id: \$id) {\n"
                . add_block_indent($orderFields, 4)
                . "\n  }\n"
                . "}",
            'variables' => array(
                'id' => shopify_gid_local('Order', $shopifyOrderId),
            ),
        );
    }

    if ($shopifyName !== '') {
        $queries[] = array(
            'type' => 'orderByIdentifier',
            'method' => 'orderByIdentifier:name',
            'query' => "query FindOrderByIdentifier(\$identifier: OrderIdentifierInput!) {\n"
                . "  orderByIdentifier(identifier: \$identifier) {\n"
                . add_block_indent($orderFields, 4)
                . "\n  }\n"
                . "}",
            'variables' => array(
                'identifier' => array(
                    'name' => $shopifyName,
                ),
            ),
        );

        $normalizedName = ltrim($shopifyName, '#');
        $searchTerms = array();
        $searchTerms[] = 'name:"' . addslashes_for_shopify_search($shopifyName) . '"';
        if ($normalizedName !== '' && $normalizedName !== $shopifyName) {
            $searchTerms[] = 'name:"' . addslashes_for_shopify_search($normalizedName) . '"';
        }

        foreach ($searchTerms as $searchTerm) {
            $queries[] = array(
                'type' => 'orders',
                'method' => 'orders:query=' . $searchTerm,
                'query' => "query FindOrdersByQuery(\$query: String!) {\n"
                    . "  orders(first: 5, query: \$query, reverse: true) {\n"
                    . "    nodes {\n"
                    . add_block_indent($orderFields, 6)
                    . "\n    }\n"
                    . "  }\n"
                    . "}",
                'variables' => array(
                    'query' => $searchTerm,
                ),
            );
        }
    }

    return $queries;
}

function extract_order_from_lookup_response($type, $raw)
{
    if (!is_array($raw)) {
        return null;
    }

    if ($type === 'orderByIdentifier') {
        return isset($raw['data']['orderByIdentifier']) ? $raw['data']['orderByIdentifier'] : null;
    }

    if ($type === 'orders') {
        if (!empty($raw['data']['orders']['nodes'][0]) && is_array($raw['data']['orders']['nodes'][0])) {
            return $raw['data']['orders']['nodes'][0];
        }
        return null;
    }

    return isset($raw['data']['order']) ? $raw['data']['order'] : null;
}

function extract_existing_fulfillments($order)
{
    if (!is_array($order)) {
        return array();
    }

    if (!empty($order['fulfillments']['nodes']) && is_array($order['fulfillments']['nodes'])) {
        return $order['fulfillments']['nodes'];
    }

    if (!empty($order['fulfillments']) && is_array($order['fulfillments'])) {
        return $order['fulfillments'];
    }

    return array();
}

function extract_fulfillment_orders($order)
{
    if (!is_array($order)) {
        return array();
    }

    if (!empty($order['fulfillmentOrders']['nodes']) && is_array($order['fulfillmentOrders']['nodes'])) {
        return $order['fulfillmentOrders']['nodes'];
    }

    return array();
}

function build_line_items_by_fulfillment_order($fulfillmentOrders)
{
    $out = array();

    foreach ($fulfillmentOrders as $fo) {
        if (empty($fo['id'])) {
            continue;
        }

        $foStatus = strtoupper((string)(isset($fo['status']) ? $fo['status'] : ''));
        if (in_array($foStatus, array('CLOSED', 'CANCELLED', 'INCOMPLETE'), true)) {
            continue;
        }

        $lineItems = array();
        if (!empty($fo['lineItems']['nodes']) && is_array($fo['lineItems']['nodes'])) {
            foreach ($fo['lineItems']['nodes'] as $li) {
                $remaining = isset($li['remainingQuantity']) ? (int)$li['remainingQuantity'] : 0;
                if (!empty($li['id']) && $remaining > 0) {
                    $lineItems[] = array(
                        'id' => $li['id'],
                        'quantity' => $remaining,
                    );
                }
            }
        }

        if (!empty($lineItems)) {
            $out[] = array(
                'fulfillmentOrderId' => $fo['id'],
                'fulfillmentOrderLineItems' => $lineItems,
            );
        }
    }

    return $out;
}

function shopify_get_access_token($forceRefresh)
{
    $cacheFile = __DIR__ . '/cache/shopify_access_token_' . md5((string)SHOPIFY_STORE_DOMAIN) . '.json';

    if (!$forceRefresh) {
        $cached = read_shopify_token_cache($cacheFile);
        if ($cached !== null) {
            return array(
                'ok' => true,
                'http_status' => 200,
                'access_token' => $cached['access_token'],
                'raw' => array('cached' => true),
            );
        }
    }

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

    $ch = curl_init($url);
    curl_setopt_array($ch, array(
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_HTTPHEADER => array(
            'Content-Type: application/json',
            'Accept: application/json',
        ),
        CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
    ));

    $body = curl_exec($ch);
    $curlErr = curl_error($ch);
    $httpStatus = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $decoded = json_decode((string)$body, true);
    if (!is_array($decoded)) {
        $decoded = array('raw_body' => $body);
    }

    if ($curlErr !== '') {
        return array(
            'ok' => false,
            'http_status' => $httpStatus,
            'raw' => $decoded,
            'error' => $curlErr,
        );
    }

    $token = isset($decoded['access_token']) ? (string)$decoded['access_token'] : '';
    if ($httpStatus < 200 || $httpStatus >= 300 || $token === '') {
        return array(
            'ok' => false,
            'http_status' => $httpStatus,
            'raw' => $decoded,
            'error' => 'Shopify did not return a valid access token.',
        );
    }

    $expiresIn = 86399;
    if (isset($decoded['expires_in'])) {
        $expiresIn = max(60, (int)$decoded['expires_in']);
    }

    write_shopify_token_cache($cacheFile, array(
        'access_token' => $token,
        'expires_at' => time() + $expiresIn - 300,
    ));

    return array(
        'ok' => true,
        'http_status' => $httpStatus,
        'access_token' => $token,
        'raw' => $decoded,
    );
}

function shopify_graphql_with_token($accessToken, $query, $variables)
{
    $url = 'https://' . SHOPIFY_STORE_DOMAIN . '/admin/api/' . SHOPIFY_API_VERSION . '/graphql.json';
    $payload = array(
        'query' => $query,
        'variables' => $variables,
    );

    $ch = curl_init($url);
    curl_setopt_array($ch, array(
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_HTTPHEADER => array(
            'Content-Type: application/json',
            'Accept: application/json',
            'X-Shopify-Access-Token: ' . $accessToken,
        ),
        CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
    ));

    $body = curl_exec($ch);
    $curlErr = curl_error($ch);
    $httpStatus = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $decoded = json_decode((string)$body, true);
    if (!is_array($decoded)) {
        $decoded = array('raw_body' => $body);
    }

    if ($curlErr !== '') {
        return array(
            'ok' => false,
            'http_status' => $httpStatus,
            'raw' => $decoded,
            'curl_error' => $curlErr,
        );
    }

    $hasTopLevelErrors = !empty($decoded['errors']);

    return array(
        'ok' => $httpStatus >= 200 && $httpStatus < 300 && !$hasTopLevelErrors,
        'http_status' => $httpStatus,
        'raw' => $decoded,
    );
}

function shopify_response_looks_like_auth_issue($raw)
{
    $json = json_encode($raw, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    if (!is_string($json) || $json === '') {
        return false;
    }

    $json = strtolower($json);
    return strpos($json, 'invalid token') !== false
        || strpos($json, 'expired') !== false
        || strpos($json, 'unauthorized') !== false
        || strpos($json, 'forbidden') !== false;
}

function read_shopify_token_cache($cacheFile)
{
    if (!is_file($cacheFile)) {
        return null;
    }

    $raw = @file_get_contents($cacheFile);
    if ($raw === false) {
        return null;
    }

    $decoded = json_decode($raw, true);
    if (!is_array($decoded)) {
        return null;
    }

    $token = (string)(isset($decoded['access_token']) ? $decoded['access_token'] : '');
    $expiresAt = (int)(isset($decoded['expires_at']) ? $decoded['expires_at'] : 0);

    if ($token === '' || $expiresAt <= time()) {
        return null;
    }

    return array(
        'access_token' => $token,
        'expires_at' => $expiresAt,
    );
}

function write_shopify_token_cache($cacheFile, $payload)
{
    $dir = dirname($cacheFile);
    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }

    @file_put_contents($cacheFile, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}

function shopify_gid_local($type, $id)
{
    return 'gid://shopify/' . $type . '/' . $id;
}

function add_block_indent($text, $spaces)
{
    $indent = str_repeat(' ', (int)$spaces);
    $lines = explode("\n", $text);
    foreach ($lines as $index => $line) {
        $lines[$index] = $indent . $line;
    }
    return implode("\n", $lines);
}

function addslashes_for_shopify_search($value)
{
    $value = str_replace('\\', '\\\\', $value);
    $value = str_replace('"', '\\"', $value);
    return $value;
}
