HEX
Server: Apache
System: Linux vpshost11508.publiccloud.com.br 5.15.179-grsec-vpshost-10.lc.el8.x86_64 #1 SMP Mon Apr 7 12:04:45 -03 2025 x86_64
User: wicomm2 (10002)
PHP: 8.3.0
Disabled: apache_child_terminate,dl,escapeshellarg,escapeshellcmd,exec,link,mail,openlog,passthru,pcntl_alarm,pcntl_exec,pcntl_fork,pcntl_get_last_error,pcntl_getpriority,pcntl_setpriority,pcntl_signal,pcntl_signal_dispatch,pcntl_sigprocmask,pcntl_sigtimedwait,pcntl_sigwaitinfo,pcntl_strerror,pcntl_wait,pcntl_waitpid,pcntl_wexitstatus,pcntl_wifexited,pcntl_wifsignaled,pcntl_wifstopped,pcntl_wstopsig,pcntl_wtermsig,php_check_syntax,php_strip_whitespace,popen,proc_close,proc_open,shell_exec,symlink,system
Upload Files
File: /home/storage/5/78/dd/wicomm2/public_html/clientes/eucatex/showcase/region.php
<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
set_time_limit(300000);
ini_set('memory_limit', '928M');

$debug = false;
$appKey = 'vtexappkey-lojaeucatex-CZXEWO';
$appToken = 'TOBKCMNYXCXZWZCNXTZAAHHUSVWPOGNJGDWHAIWNOTGKGUWHRFHRYRLFHRYRLFJSEOFPDPNEWZWDIEWXNWNLWSXVPODRWJDDQSPLQGGCYQLKSAVHBVEINUONIAEPNOSUCTYVOX';
$accountName = 'lojaeucatex';
$environment = 'vtexcommercestable';

$EXT_EMAIL = 'jefferson@wicomm.com.br';
$EXT_API_KEY = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcm92aWRlcl9pZCI6MSwiZW1haWwiOiJqZWZmZXJzb25Ad2ljb21tLmNvbS5iciJ9.zprWd1UZ9hv_5fjmy3TO63kE4f27vBFKHw-kW8ieEAg';

$action = isset($_GET['action']) ? $_GET['action'] : null;

/**
 * Busca se existe um ranking cacheado na entidade MC para o SKU e CEP informado.
 * O CEP pode ser um range, então verifica se está dentro do intervalo.
 *
 * @param int $skuId
 * @param string $cep
 * @return array|null
 */
function getCachedRankFromMC($skuId, $cep)
{
    global $accountName, $environment;
    $entity = 'MC';
    $cepNum = intval(preg_replace('/[^0-9]/', '', $cep));
    // Considera que MC pode ter campos: skuId, cepStart, cepEnd, ou simplesmente cep
    // Busca por registros onde skuId bate e o cep está dentro do range (ou igual)
    $where = "(skuId=$skuId AND ((cepStart<=$cepNum AND cepEnd>=$cepNum) OR cep=\"$cepNum\"))";
    $url = "https://$accountName.$environment.com.br/api/dataentities/$entity/search?_where=" . urlencode($where) . "&_fields=payload,cep,cepStart,cepEnd";
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
    $result = curl_exec($ch);
    curl_close($ch);
    $docs = json_decode($result, true);
    if ($docs && is_array($docs) && count($docs) > 0 && isset($docs[0]['payload'])) {
        $payload = json_decode($docs[0]['payload'], true);
        return is_array($payload) ? $payload : null;
    }
    return null;
}

/**
 * Salva ou atualiza o ranking de sellers para SKU + CEP na entidade MC (Master Cache).
 *
 * @param int $skuId
 * @param string $cep
 * @param array $payload
 */

function saveRankToMC($skuId, $cep, $payload)
{
    global $accountName, $environment;
    $entity = 'MC';
    $cepNum = intval(preg_replace('/[^0-9]/', '', $cep));

    // Busca simples: apenas por skuId e cep exato (não usa intervalos)
    $where = "_where=skuId=$skuId AND cep=\"$cepNum\"";
    $searchUrl = "https://$accountName.$environment.com.br/api/dataentities/$entity/search?$where&_fields=id";
    error_log("[saveRankToMC] Buscando registro MC: $searchUrl");
    $ch = curl_init($searchUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
    $result = curl_exec($ch);
    curl_close($ch);
    $docs = json_decode($result, true);
    if ($docs === null) {
        error_log("[saveRankToMC] ERRO ao decodificar resposta de busca MC: " . var_export($result, true));
    }

    // Payload JSON corretamente formatado, inclui campos obrigatórios
    $data = [
        'skuId' => $skuId,
        'cep' => (string) $cepNum,
        'payload' => json_encode($payload, JSON_UNESCAPED_UNICODE),
        'cepStart' => null,
        'cepEnd' => null
    ];

    // Atualiza se existe, senão cria novo
    if ($docs && is_array($docs) && count($docs) > 0 && isset($docs[0]['id'])) {
        $docId = $docs[0]['id'];
        $updateUrl = "https://$accountName.$environment.com.br/api/dataentities/$entity/documents/$docId";
        error_log("[saveRankToMC] Atualizando registro existente MC id=$docId");
        $ch = curl_init($updateUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE));
        $res = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        $updateResult = json_decode($res, true);
        if ($httpCode >= 200 && $httpCode < 300) {
            error_log("[saveRankToMC] Atualização MC OK: id=$docId");
        } else {
            error_log("[saveRankToMC] ERRO ao atualizar MC id=$docId: HTTP $httpCode - $res");
        }
        return [
            'type' => 'update',
            'id' => $docId,
            'httpCode' => $httpCode,
            'result' => $updateResult
        ];
    } else {
        $url = "https://$accountName.$environment.com.br/api/dataentities/$entity/documents";
        error_log("[saveRankToMC] Criando novo registro MC (skuId=$skuId, cep=$cepNum)");
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE));
        $res = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        $insertResult = json_decode($res, true);
        if ($httpCode >= 200 && $httpCode < 300) {
            error_log("[saveRankToMC] Inserção MC OK: skuId=$skuId, cep=$cepNum");
        } else {
            error_log("[saveRankToMC] ERRO ao inserir MC: HTTP $httpCode - $res");
        }
        return [
            'type' => 'insert',
            'httpCode' => $httpCode,
            'result' => $insertResult
        ];
    }
}

/**
 * Set VTEX headers
 */
function vtexHeaders()
{
    global $appKey, $appToken;
    return [
        "X-VTEX-API-AppKey: $appKey",
        "X-VTEX-API-AppToken: $appToken",
        "Content-Type: application/json"
    ];
}

/**
 * Busca EANs da vitrine via API externa
 *
 * @param string $cep CEP
 * @return array Lista de EANs
 */
function getEansFromShowcase($cep)
{
    $url = "https://lojaeucatexapi.conectala.com.br/app/Api/V1/SellerRank/showcase";
    $headers = [
        "Content-Type: application/json",
        "accept: application/json;charset=UTF-8",
        "x-email: jefferson@wicomm.com.br",
        "x-api-key: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcm92aWRlcl9pZCI6MSwiZW1haWwiOiJqZWZmZXJzb25Ad2ljb21tLmNvbS5iciJ9.zprWd1UZ9hv_5fjmy3TO63kE4f27vBFKHw-kW8ieEAg",
        "x-provider-key: 5"
    ];
    $payload = [
        "marketplace" => "Wicommpartnerbr",
        "cep" => preg_replace('/[^0-9]/', '', $cep)
    ];
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    $result = curl_exec($ch);
    curl_close($ch);
    $data = json_decode($result, true);
    $eans = [];
    if (is_array($data)) {
        $isFlatArray = count($data) > 0 && is_string(reset($data));
        if ($isFlatArray) {
            foreach ($data as $ean) {
                if (is_string($ean) && $ean !== '')
                    $eans[] = $ean;
            }
        } elseif (isset($data['data']) && is_array($data['data'])) {
            foreach ($data['data'] as $ean) {
                if (is_string($ean) && $ean !== '')
                    $eans[] = $ean;
            }
        } elseif (isset($data['products']) && is_array($data['products'])) {
            foreach ($data['products'] as $product) {
                if (isset($product['ean']) && is_string($product['ean']) && $product['ean'] !== '') {
                    $eans[] = $product['ean'];
                }
            }
        }
    }
    if (empty($eans)) {
        error_log("SHOWCASE: nenhum EAN retornado");
    } else {
        error_log("SHOWCASE: EANs recebidos");
    }
    return $eans;
}

/**
 * Busca SKUs na VTEX por uma lista de EANs
 *
 * @param array $eans Lista de EANs
 * @return array Lista de SKUs encontrados
 */
function getSkusByEans($eans)
{
    global $accountName, $environment, $debug;
    $skus = [];
    $totalFound = 0;

    $eans = array_slice($eans, 0, 50); // segurança para evitar overload

    foreach ($eans as $ean) {
        error_log("PROCESSANDO EAN: " . $ean);

        $url = "https://$accountName.$environment.com.br/api/catalog_system/pub/products/search?fq=alternateIds_Ean:$ean";
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
        $result = curl_exec($ch);
        curl_close($ch);
        $data = json_decode($result, true);

        if (!$data || !is_array($data) || count($data) === 0) {
            $url = "https://$accountName.$environment.com.br/api/catalog_system/pub/products/search?fq=referenceId:$ean";
            $ch2 = curl_init($url);
            curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch2, CURLOPT_HTTPHEADER, vtexHeaders());
            $result = curl_exec($ch2);
            curl_close($ch2);
            $data = json_decode($result, true);
        }

        if ($data && is_array($data)) {
            $eanCount = 0;
            foreach ($data as $product) {
                if (isset($product['items']) && is_array($product['items']) && count($product['items']) > 0) {
                    foreach ($product['items'] as $item) {
                        $skus[] = $item;
                        $eanCount++;
                    }
                }
            }
            $totalFound += $eanCount;
            error_log("EAN {$ean}: {$eanCount} SKUs encontrados");
        }

        usleep(150000); // delay leve entre chamadas
        unset($data);
        gc_collect_cycles();
    }

    error_log("SEARCH BY EAN: consultas concluídas (" . count($eans) . "), SKUs encontrados: {$totalFound}");
    error_log("SEARCH BY EAN: total de SKUs salvos após simulação: " . count($skus));
    return $skus;
}

/**
 * Busca SKUs do catálogo VTEX (nova versão usando endpoint /api/catalog_system/pub/products/search)
 *
 * @param int $batchSize Número de produtos por página
 * @param int $maxBatches Número máximo de páginas a buscar
 * @return array Lista de SKUs
 */
function getSkus($batchSize = 50, $maxBatches = 200)
{
    global $accountName, $environment, $debug;
    $skus = [];
    $allIds = [];
    $from = 0;
    $loopCount = 0;

    while ($loopCount < $maxBatches) {
        $to = $from + $batchSize - 1;
        $url = "https://$accountName.$environment.com.br/api/catalog_system/pub/products/search?from=$from&to=$to";
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
        $result = curl_exec($ch);
        curl_close($ch);
        $data = json_decode($result, true);

        if (!$data || !is_array($data) || count($data) === 0) {
            break;
        }

        foreach ($data as $product) {
            if (isset($product['items']) && is_array($product['items'])) {
                foreach ($product['items'] as $item) {
                    if (isset($item['itemId'])) {
                        $allIds[] = $item['itemId'];
                    }
                }
            }
        }

        $loopCount++;
        $from += $batchSize;

        if (count($data) < $batchSize) {
            break;
        }
    }

    error_log("CATALOG SEARCH: concluído");

    foreach ($allIds as $skuId) {
        $skuUrl = "https://$accountName.$environment.com.br/api/catalog_system/pvt/sku/stockkeepingunitbyid/$skuId";
        $ch3 = curl_init($skuUrl);
        curl_setopt($ch3, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch3, CURLOPT_HTTPHEADER, vtexHeaders());
        $skuResult = curl_exec($ch3);
        curl_close($ch3);
        if ($skuResult)
            $skus[] = json_decode($skuResult, true);
    }

    return $skus;
}

/**
 * Função para simular entrega de um SKU por Seller e CEP
 * Nova versão: re-simula se não houver SLA, usa salesChannel=1 como fallback, logs detalhados.
 */
function simulateDelivery($skuId, $sellerId, $cep)
{
    global $accountName, $environment;
    error_log("SIMULATION: testando SKU {$skuId}, seller {$sellerId}");

    $salesChannel = isset($_GET['sc']) && $_GET['sc'] !== '' ? (string) $_GET['sc'] : '1';
    $url = "https://$accountName.$environment.com.br/api/checkout/pub/orderForms/simulation";
    $payload = [
        "items" => [
            [
                "id" => (string) $skuId,
                "quantity" => 1,
                "seller" => (string) $sellerId
            ]
        ],
        "postalCode" => preg_replace('/[^0-9]/', '', $cep),
        "country" => "BRA",
        "salesChannel" => $salesChannel
    ];

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    $result = curl_exec($ch);
    curl_close($ch);

    if (!$result) {
        error_log("SIMULATION FAIL: sem retorno para SKU {$skuId}, seller {$sellerId}");
        return null;
    }

    $data = json_decode($result, true);
    if (!$data || !isset($data['items'][0])) {
        error_log("SIMULATION FAIL: retorno inválido para SKU {$skuId}, seller {$sellerId}");
        return null;
    }

    $availability = $data['items'][0]['availability'] ?? 'unknown';
    $sellingPrice = $data['items'][0]['sellingPrice'] ?? null;
    // NOVA LÓGICA DE SLAS
    $slas = [];
    if (isset($data['shippingData']['logisticsInfo'][0]['slas'])) {
        $slas = $data['shippingData']['logisticsInfo'][0]['slas'];
    } elseif (isset($data['logisticsInfo'][0]['slas'])) {
        // fallback: alguns ambientes VTEX retornam direto em logisticsInfo
        $slas = $data['logisticsInfo'][0]['slas'];
    } elseif (isset($data['purchaseConditions']['itemPurchaseConditions'][0]['slas'])) {
        // fallback adicional baseado em purchaseConditions
        $slas = $data['purchaseConditions']['itemPurchaseConditions'][0]['slas'];
    }
    $best = null;

    // Se não houver SLAs, tenta novamente com salesChannel=1 e loga
    if (empty($slas)) {
        error_log("SIMULATION WARNING: nenhum SLA retornado para seller {$sellerId}, tentando re-simulação com salesChannel=1");
        $payload['salesChannel'] = "1";
        $ch2 = curl_init($url);
        curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch2, CURLOPT_HTTPHEADER, vtexHeaders());
        curl_setopt($ch2, CURLOPT_POST, true);
        curl_setopt($ch2, CURLOPT_POSTFIELDS, json_encode($payload));
        $retryResult = curl_exec($ch2);
        curl_close($ch2);
        if ($retryResult) {
            $retryData = json_decode($retryResult, true);
            // Repete a lógica de fallback de SLAs após re-simulação
            if (isset($retryData['shippingData']['logisticsInfo'][0]['slas'])) {
                $slas = $retryData['shippingData']['logisticsInfo'][0]['slas'];
            } elseif (isset($retryData['logisticsInfo'][0]['slas'])) {
                $slas = $retryData['logisticsInfo'][0]['slas'];
            } elseif (isset($retryData['purchaseConditions']['itemPurchaseConditions'][0]['slas'])) {
                $slas = $retryData['purchaseConditions']['itemPurchaseConditions'][0]['slas'];
            }
            if (!empty($slas)) {
                $data = $retryData;
                error_log("SIMULATION RECOVERED: SLA encontrado após re-simulação para seller {$sellerId}");
            }
        }
    }

    if (is_array($slas) && count($slas) > 0) {
        foreach ($slas as $sla) {
            $candidate = [
                'name' => $sla['name'],
                'price' => $sla['price'] ?? $sellingPrice,
                'shippingEstimate' => $sla['shippingEstimate'] ?? 'indefinido',
                'deliveryIds' => $sla['deliveryIds'] ?? []
            ];
            if (
                $best === null
                || $candidate['price'] < $best['price']
                || ($candidate['price'] == $best['price']
                    && estimateToMinutes($candidate['shippingEstimate']) < estimateToMinutes($best['shippingEstimate']))
            ) {
                $best = $candidate;
            }
        }
    } elseif ($sellingPrice !== null) {
        $best = [
            'name' => 'Sem cálculo de frete',
            'price' => $sellingPrice,
            'shippingEstimate' => 'indefinido',
            'deliveryIds' => []
        ];
    }

    // Se ainda não achou SLAs mas o produto tem entrega (purchaseConditions), tenta recuperar manualmente
    if ((!$best || empty($best['shippingEstimate']) || strtolower($best['shippingEstimate']) === 'indefinido') && isset($data['purchaseConditions']['itemPurchaseConditions'][0]['slas'])) {
        $manualSlas = $data['purchaseConditions']['itemPurchaseConditions'][0]['slas'];
        if (is_array($manualSlas) && count($manualSlas) > 0) {
            $firstSla = $manualSlas[0];
            $best = [
                'name' => $firstSla['name'] ?? 'Entrega própria',
                'price' => $sellingPrice,
                'shippingEstimate' => $firstSla['shippingEstimate'] ?? '3bd',
                'deliveryIds' => $firstSla['deliveryIds'] ?? [],
                'availability' => 'available'
            ];
            error_log("SIMULATION RECOVERED (purchaseConditions): SKU {$skuId}, seller {$sellerId}, shipping={$best['shippingEstimate']}");
        }
    }

    // Parcelamento
    $installments = $data['paymentData']['installmentOptions'][0]['installments'][0]['count'] ?? null;
    $installmentValue = $data['paymentData']['installmentOptions'][0]['installments'][0]['value'] ?? null;
    $paymentName = $data['paymentData']['installmentOptions'][0]['paymentName'] ?? null;

    if ($best) {
        $best['installments'] = $installments;
        $best['installmentValue'] = $installmentValue;
        $best['paymentName'] = $paymentName;
        $best['availability'] = $availability;
        error_log("SIMULATION OK: SKU {$skuId}, seller {$sellerId}, price={$best['price']}, shipping={$best['shippingEstimate']}");
    } else {
        error_log("SIMULATION FAIL FINAL: SKU {$skuId}, seller {$sellerId}, sem opções de entrega");
    }

    return $best;
}

/**
 * Função para salvar ou atualizar dados no Master Data v1 (entidade 'RA')
 */
function saveMasterData($data)
{
    global $accountName, $environment;
    $entity = 'RA';
    $url = "https://$accountName.$environment.com.br/api/dataentities/$entity/documents";
    $where = "skuId={$data['skuId']} AND sellerId=\"{$data['sellerId']}\" AND cep=\"{$data['cep']}\"";
    $searchUrl = $url . "?_where=" . urlencode($where) . "&_fields=id";
    $ch = curl_init($searchUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
    $result = curl_exec($ch);
    curl_close($ch);
    $docs = json_decode($result, true);
    if ($docs && count($docs) > 0 && isset($docs[0]['id'])) {
        $docId = $docs[0]['id'];
        $updateUrl = "https://$accountName.$environment.com.br/api/dataentities/$entity/documents/$docId";
        $ch = curl_init($updateUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        $res = curl_exec($ch);
        curl_close($ch);
        return json_decode($res, true);
    } else {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        $res = curl_exec($ch);
        curl_close($ch);
        return json_decode($res, true);
    }
}

/**
 * Função para buscar o melhor seller para um SKU e CEP
 */
function getBestSeller($skuId, $cep)
{
    global $accountName, $environment;
    $entity = 'RA';
    $url = "https://$accountName.$environment.com.br/api/dataentities/$entity/search?_where=skuId=$skuId AND cep=\"$cep\"&_fields=sellerId,price,shippingEstimate";
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
    $result = curl_exec($ch);
    curl_close($ch);
    $docs = json_decode($result, true);
    $best = null;
    if ($docs && count($docs) > 0) {
        foreach ($docs as $doc) {
            if (!isset($doc['price']))
                continue;
            if ($best === null || $doc['price'] < $best['price'] || ($doc['price'] == $best['price'] && $doc['shippingEstimate'] < $best['shippingEstimate'])) {
                $best = $doc;
            }
        }
    }
    return $best;
}

/**
 * Converte estimativa VTEX (ex: "2bd", "3d", "48h") para minutos comparáveis
 *
 * @param string $estimate
 * @return int
 */
function estimateToMinutes($estimate)
{
    if (!$estimate || !is_string($estimate))
        return PHP_INT_MAX;
    if (trim(strtolower($estimate)) === 'indefinido')
        return PHP_INT_MAX - 1;
    if (!preg_match('/^(\d+)\s*([a-zA-Z]+)/', $estimate, $m))
        return PHP_INT_MAX;
    $n = intval($m[1]);
    $u = strtolower($m[2]);
    if ($u === 'bd')
        return $n * 1440; // business day ~ 1 dia
    if ($u === 'd')
        return $n * 1440;
    if ($u === 'h')
        return $n * 60;
    if ($u === 'm')
        return $n;
    return PHP_INT_MAX;
}

/**
 * Busca sellers elegíveis a partir da entidade DS pelo CEP (intervalo postalCodeStart..postalCodeEnd)
 *
 * @param string $cep
 * @return array
 */
function getSellersFromDS($cep)
{
    global $accountName, $environment;
    $entity = 'DS';
    $cepNum = intval(preg_replace('/[^0-9]/', '', $cep));
    $where = "postalCodeStart<=$cepNum AND postalCodeEnd>=$cepNum";
    $url = "https://$accountName.$environment.com.br/api/dataentities/$entity/search?_where=" . urlencode($where) . "&_fields=sellerId,sellerName,score&_sort=score ASC&_size=200";
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
    $result = curl_exec($ch);
    curl_close($ch);
    $docs = json_decode($result, true);
    if (!$docs || !is_array($docs))
        return [];
    $out = [];
    foreach ($docs as $d) {
        $out[] = [
            'sellerId' => $d['sellerId'] ?? null,
            'sellerName' => $d['sellerName'] ?? null,
            'score' => isset($d['score']) ? intval($d['score']) : PHP_INT_MAX,
        ];
    }
    return $out;
}

/**
 * Ranqueia sellers (DS) para um SKU com base em score, tempo de entrega e preço
 * Retorna todos os sellers válidos, adiciona availability e source, salva no Master Data sellers disponíveis e loga progresso.
 * O retorno agora inclui um array 'meta' com informações extras.
 *
 * @param int $skuId
 * @param string $cep
 * @return array
 */
function rankSellersForSku($skuId, $cep)
{
    global $accountName, $environment;
    $candidates = getSellersFromDS($cep);
    $source = 'ds';

    if (empty($candidates)) {
        $fallbackUrl = "https://$accountName.$environment.com.br/api/catalog_system/pub/products/search?fq=skuId:$skuId";
        $ch = curl_init($fallbackUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
        $fallbackResult = curl_exec($ch);
        curl_close($ch);
        $fallbackData = json_decode($fallbackResult, true);
        if ($fallbackData && isset($fallbackData[0]['items'][0]['sellers'])) {
            foreach ($fallbackData[0]['items'][0]['sellers'] as $seller) {
                $candidates[] = [
                    'sellerId' => $seller['sellerId'] ?? null,
                    'sellerName' => $seller['sellerName'] ?? ($seller['SellerName'] ?? 'Sem nome'),
                    'score' => 999
                ];
            }
            $source = 'catalog_fallback';
            error_log("RANK FALLBACK: sellers carregados do catálogo VTEX");
        }
    }

    $ranked = [];
    foreach ($candidates as $c) {
        if (empty($c['sellerId']))
            continue;
        $sim = simulateDelivery($skuId, $c['sellerId'], $cep);
        if ($sim) {
            $isDeliverable = isset($sim['shippingEstimate'])
                && strtolower($sim['shippingEstimate']) !== 'indefinido'
                && strtolower($sim['availability']) === 'available'
                && !empty($sim['name'])
                && strtolower($sim['name']) !== 'sem cálculo de frete';

            if (!$isDeliverable) {
                error_log("SKIP SELLER: {$c['sellerId']} sem entrega válida (estimate={$sim['shippingEstimate']}, sla={$sim['name']})");
                continue;
            }

            $ranked[] = [
                'sellerId' => $c['sellerId'],
                'sellerName' => $c['sellerName'] ?? 'Sem nome',
                'score' => $c['score'] ?? 999,
                'price' => $sim['price'],
                'shippingEstimate' => $sim['shippingEstimate'],
                'slaName' => $sim['name'],
                'availability' => 'available',
                'source' => $source,
                'estimateMinutes' => estimateToMinutes($sim['shippingEstimate'])
            ];

            saveMasterData([
                'skuId' => $skuId,
                'sellerId' => $c['sellerId'],
                'cep' => $cep,
                'price' => $sim['price'],
                'shippingEstimate' => $sim['shippingEstimate'],
                'slaName' => $sim['name']
            ]);
        }
    }

    usort($ranked, function ($a, $b) {
        if ($a['score'] !== $b['score'])
            return $a['score'] <=> $b['score'];
        $aIndef = ($a['estimateMinutes'] >= (PHP_INT_MAX - 2));
        $bIndef = ($b['estimateMinutes'] >= (PHP_INT_MAX - 2));
        if ($aIndef && !$bIndef)
            return 1;
        if (!$aIndef && $bIndef)
            return -1;
        if ($a['estimateMinutes'] !== $b['estimateMinutes'])
            return $a['estimateMinutes'] <=> $b['estimateMinutes'];
        return $a['price'] <=> $b['price'];
    });

    error_log("RANK: {$skuId} total " . count($ranked) . " sellers ranqueados");
    return [
        'ranked' => $ranked,
        'meta' => [
            'sourceUsed' => $source,
            'totalCandidates' => count($candidates),
            'totalRanked' => count($ranked),
            'fallbackTriggered' => ($source === 'catalog_fallback')
        ]
    ];
}

switch ($action) {
    case 'cron':
        $cep = isset($_GET['cep']) ? $_GET['cep'] : '01001000';
        error_log("CRON: iniciado");
        $eans = getEansFromShowcase($cep);
        $eans = array_slice($eans, 50, 80);
        $skus = getSkusByEans($eans);
        $skus = array_slice($skus, 0, 10);
        $results = [];
        $totalSkus = count($skus);
        $totalSellers = 0;
        $totalSimulations = 0;
        foreach ($skus as $sku) {
            $skuId = $sku['itemId'] ?? $sku['Id'] ?? null;
            if (!$skuId)
                continue;

            $skuDetailUrl = "https://$accountName.$environment.com.br/api/catalog_system/pvt/sku/stockkeepingunitbyid/$skuId";
            $chDetail = curl_init($skuDetailUrl);
            curl_setopt($chDetail, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($chDetail, CURLOPT_HTTPHEADER, vtexHeaders());
            $skuDetailResult = curl_exec($chDetail);
            curl_close($chDetail);

            $skuDetail = json_decode($skuDetailResult, true);
            // if (!$skuDetail || !isset($skuDetail['IsActive']) || !$skuDetail['IsActive'] || !$skuDetail['ProductIsVisible']) continue;

            $skuSellers = [];
            if (isset($skuDetail['SkuSellers']) && is_array($skuDetail['SkuSellers'])) {
                $skuSellers = $skuDetail['SkuSellers'];
            } elseif (isset($skuDetail['Sellers']) && is_array($skuDetail['Sellers'])) {
                $skuSellers = $skuDetail['Sellers'];
            }

            if (count($skuSellers) === 0) {
                // Tenta buscar sellers pelo endpoint público
                $fallbackUrl = "https://$accountName.$environment.com.br/api/catalog_system/pub/products/search?fq=skuId:$skuId";
                $chFallback = curl_init($fallbackUrl);
                curl_setopt($chFallback, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($chFallback, CURLOPT_HTTPHEADER, vtexHeaders());
                $fallbackResult = curl_exec($chFallback);
                curl_close($chFallback);
                $fallbackData = json_decode($fallbackResult, true);

                if ($fallbackData && isset($fallbackData[0]['items'][0]['sellers'])) {
                    $skuSellers = $fallbackData[0]['items'][0]['sellers'];
                    error_log("FALLBACK: sellers encontrados via search para SKU {$skuId}");
                } else {
                    // Força seller padrão 1 para teste de simulação
                    $skuSellers = [['SellerId' => '1']];
                    error_log("FALLBACK: nenhum seller encontrado, forçando sellerId=1 para SKU {$skuId}");
                }
            }

            // Força sellerId=1 para teste completo de simulação e salvamento
            foreach ($skuSellers as $seller) {
                $sellerId = $seller['SellerId'] ?? '1';
                $totalSellers++;
                $sim = simulateDelivery($skuId, $sellerId, $cep);
                $totalSimulations++;
                if ($sim) {
                    $data = [
                        'skuId' => $skuId,
                        'sellerId' => $sellerId,
                        'cep' => $cep,
                        'price' => $sim['price'],
                        'shippingEstimate' => $sim['shippingEstimate'],
                        'slaName' => $sim['name']
                    ];
                    saveMasterData($data);
                    $results[] = $data;
                }
            }
        }
        error_log("CRON: finalizado processed=" . count($results) . " skus=" . $totalSkus . " sellers=" . $totalSellers . " simulations=" . $totalSimulations);
        header('Content-Type: application/json');
        $output = [
            'processed' => count($results),
            'totalSkus' => $totalSkus,
            'totalSellers' => $totalSellers,
            'totalSimulations' => $totalSimulations,
            'results' => $results
        ];
        echo json_encode($output, JSON_PRETTY_PRINT);
        break;

    case 'seller':
        $skuId = isset($_GET['skuId']) ? intval($_GET['skuId']) : null;
        $cep = isset($_GET['cep']) ? $_GET['cep'] : null;
        if (!$skuId || !$cep) {
            http_response_code(400);
            echo json_encode(['error' => 'skuId and cep are required']);
            exit;
        }
        $best = getBestSeller($skuId, $cep);
        header('Content-Type: application/json');
        if ($best) {
            echo json_encode($best);
        } else {
            echo json_encode(['error' => 'No seller found']);
        }
        break;

    case 'rank':
        $skuId = isset($_GET['skuId']) ? intval($_GET['skuId']) : null;
        $cep = isset($_GET['cep']) ? $_GET['cep'] : null;
        if (!$skuId || !$cep) {
            http_response_code(400);
            echo json_encode(['error' => 'skuId and cep are required']);
            break;
        }
        // 1. Tenta cache MC
        $cached = getCachedRankFromMC($skuId, $cep);
        if (is_array($cached) && isset($cached['sellers']) && is_array($cached['sellers'])) {
            header('Content-Type: application/json');
            echo json_encode([
                'skuId' => $skuId,
                'cep' => preg_replace('/[^0-9]/', '', $cep),
                'count' => isset($cached['count']) ? $cached['count'] : count($cached['sellers']),
                'sellers' => $cached['sellers'],
                'explanation' => [
                    'sourceUsed' => 'cache_MC'
                ]
            ], JSON_PRETTY_PRINT);
            break;
        }
        // 2. Não tem cache: executa ranking e salva no MC
        $rankInfo = rankSellersForSku($skuId, $cep);
        $ranked = $rankInfo['ranked'];
        $meta = $rankInfo['meta'];
        // Formata sellers (igual ao retorno atual)
        $sellersArr = array_map(function ($r) {
            return [
                'sellerId' => $r['sellerId'],
                'sellerName' => $r['sellerName'],
                'score' => $r['score'],
                'price' => $r['price'],
                'shippingEstimate' => $r['shippingEstimate'],
                'slaName' => $r['slaName'],
                'availability' => $r['availability'],
                'source' => $r['source']
            ];
        }, $ranked);
        $payloadToSave = [
            'count' => count($sellersArr),
            'sellers' => $sellersArr,
            'meta' => $meta
        ];
        saveRankToMC($skuId, $cep, $payloadToSave);
        header('Content-Type: application/json');
        echo json_encode([
            'skuId' => $skuId,
            'cep' => preg_replace('/[^0-9]/', '', $cep),
            'count' => count($sellersArr),
            'sellers' => $sellersArr,
            'explanation' => [
                'sourceUsed' => $meta['sourceUsed'],
                'totalCandidates' => $meta['totalCandidates'],
                'totalRanked' => $meta['totalRanked'],
                'fallbackTriggered' => $meta['fallbackTriggered']
            ]
        ], JSON_PRETTY_PRINT);
        break;

    default:
        http_response_code(404);
        echo json_encode(['error' => 'Invalid or missing action parameter']);
        break;
}

// FIM DO ARQUIVO