<?php
require_once __DIR__ . '/../core/config.php';
require_once __DIR__ . '/../core/database.php';
require_once __DIR__ . '/../services/billing/BillingRuntimeGate.php';

date_default_timezone_set('UTC');
$root = dirname(__DIR__);
$checks = array();
$failures = array();

function v617_check(&$checks, &$failures, $label, $ok, $detail = '') {
    $checks[] = array($label, (bool)$ok, $detail);
    if (!$ok) $failures[] = $label . ($detail !== '' ? ': ' . $detail : '');
}
function v617_file($root, $relative) {
    $path = $root . '/' . $relative;
    return is_file($path) ? (string)file_get_contents($path) : '';
}
function v617_has_all($text, array $needles) {
    foreach ($needles as $needle) if (strpos($text, $needle) === false) return false;
    return true;
}

$pdo = db();
$dbName = (string)$pdo->query('SELECT DATABASE()')->fetchColumn();
$recv = v617_file($root, 'services/billing/BillingReceivablesService.php');
$pay = v617_file($root, 'services/billing/BillingPayablesService.php');
$recvEndpoint = v617_file($root, 'modules/billing/receivable_bulk_payment_ajax.php');
$payEndpoint = v617_file($root, 'modules/billing/payable_bulk_payment_ajax.php');
$recvUi = v617_file($root, 'modules/billing/receivables.js');
$payUi = v617_file($root, 'modules/billing/payables.php');

v617_check($checks, $failures, 'database target', $dbName === 'icylab_app', $dbName);

$stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='billing_payment_batches' AND COLUMN_NAME='idempotency_key'");
$stmt->execute();
$columnRows = (int)$stmt->fetchColumn();
v617_check($checks, $failures, 'idempotency column', $columnRows === 1, 'rows=' . $columnRows);

$stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='billing_payment_batches' AND INDEX_NAME='uq_payment_batch_idempotency' AND NON_UNIQUE=0");
$stmt->execute();
$indexRows = (int)$stmt->fetchColumn();
v617_check($checks, $failures, 'unique idempotency index', $indexRows >= 1, 'rows=' . $indexRows);

v617_check($checks, $failures, 'receivable production bulk active',
    strpos($recv, 'Los cobros masivos productivos permanecen deshabilitados') === false
    && strpos($recvUi, 'Los cobros masivos productivos permanecen deshabilitados') === false);
v617_check($checks, $failures, 'payable production bulk active',
    strpos($pay, 'Los pagos masivos productivos permanecen deshabilitados') === false
    && strpos($payUi, 'Los pagos masivos productivos permanecen deshabilitados') === false);

v617_check($checks, $failures, 'receivable bulk runtime gate', strpos($recv, "billing_assert_runtime_operation('finance', \$environment, 'Registro masivo de pagos recibidos')") !== false);
v617_check($checks, $failures, 'payable bulk runtime gate', strpos($pay, "billing_assert_runtime_operation('finance', \$environment, 'Registro masivo de pagos a proveedores')") !== false);

v617_check($checks, $failures, 'receivable bulk requires existing CxC',
    v617_has_all($recv, array('normalizeReceivableBulkSnapshot', 'getCollectionByDocumentId', 'cuenta por cobrar materializada'))
    && strpos(substr($recv, strpos($recv, 'public function registerBulkPayment'), 12000), 'ensureReceivableCollection(') === false);
v617_check($checks, $failures, 'payable bulk requires existing CxP',
    v617_has_all($pay, array('normalizePayableBulkSnapshot', 'getPayableCollectionByInboxId', 'cuenta por pagar materializada'))
    && strpos(substr($pay, strpos($pay, 'public function registerBulkPayment'), 12000), 'ensurePayableCollection(') === false);

$bulkGuards = array('selection_snapshot', 'idempotency_key', 'expected_balance', 'FOR UPDATE', 'lockPaymentAccount', 'assertPaymentReferenceAvailable');
v617_check($checks, $failures, 'receivable bulk snapshot/idempotency/locking', v617_has_all($recv, $bulkGuards));
v617_check($checks, $failures, 'payable bulk snapshot/idempotency/locking', v617_has_all($pay, $bulkGuards));
v617_check($checks, $failures, 'receivable bulk replay validation', v617_has_all($recv, array('findReceivableBulkReplay', 'billing_payment_allocations', 'idempotent_replay')));
v617_check($checks, $failures, 'payable bulk replay validation', v617_has_all($pay, array('findPayableBulkReplay', 'billing_payment_allocations', 'idempotent_replay')));

v617_check($checks, $failures, 'receivable endpoint explicit permission',
    strpos($recvEndpoint, "user_can('billing.receivables.bulk_collect')") !== false
    && strpos($recvEndpoint, "user_can('module.billing')") === false);
v617_check($checks, $failures, 'payable endpoint explicit permission',
    strpos($payEndpoint, "user_can('billing.payables.bulk_pay')") !== false
    && strpos($payEndpoint, "user_can('module.billing')") === false);
v617_check($checks, $failures, 'bulk endpoints forward safety payload',
    v617_has_all($recvEndpoint, array('selection_snapshot', 'idempotency_key'))
    && v617_has_all($payEndpoint, array('selection_snapshot', 'idempotency_key')));

$uiGuards = array('bulkRequestKey', 'selection_snapshot', 'idempotency_key', 'window.confirm', 'referencia', 'atómico');
v617_check($checks, $failures, 'receivable bulk UI guarded', v617_has_all($recvUi, $uiGuards));
v617_check($checks, $failures, 'payable bulk UI guarded', v617_has_all($payUi, $uiGuards));

$readAllowed = $financeBlocked = $issueBlocked = false;
try { billing_assert_runtime_operation('read', 'production', 'v6.17 diagnostic'); $readAllowed = true; } catch (Throwable $e) {}
try { billing_assert_runtime_operation('finance', 'production', 'v6.17 diagnostic'); } catch (Throwable $e) { $financeBlocked = true; }
try { billing_assert_runtime_operation('issue', 'production', 'v6.17 diagnostic'); } catch (Throwable $e) { $issueBlocked = true; }
v617_check($checks, $failures, 'production read allowed', $readAllowed);
v617_check($checks, $failures, 'production finance blocked before final activation', $financeBlocked);
v617_check($checks, $failures, 'production issue blocked', $issueBlocked);

$inventory = $pdo->query("SELECT
 (SELECT COUNT(*) FROM facto_documents WHERE BINARY environment=BINARY 'production') documents,
 (SELECT COUNT(*) FROM facto_inbox_documents WHERE BINARY environment=BINARY 'production') inbox_documents,
 (SELECT COUNT(*) FROM billing_collections WHERE BINARY environment=BINARY 'production' AND BINARY direction=BINARY 'receivable') receivables,
 (SELECT COUNT(*) FROM billing_collections WHERE BINARY environment=BINARY 'production' AND BINARY direction=BINARY 'payable') payables,
 (SELECT COUNT(*) FROM billing_payments WHERE BINARY environment=BINARY 'production') payments")->fetch(PDO::FETCH_ASSOC);
v617_check($checks, $failures, 'controlled inventory',
    (int)$inventory['documents'] === 264
    && (int)$inventory['inbox_documents'] === 520
    && (int)$inventory['receivables'] === 246
    && (int)$inventory['payables'] === 1
    && (int)$inventory['payments'] === 112,
    json_encode($inventory));

$duplicateKeys = (int)$pdo->query("SELECT COUNT(*) FROM (
 SELECT environment,direction,idempotency_key,COUNT(*) c
 FROM billing_payment_batches
 WHERE BINARY environment=BINARY 'production' AND idempotency_key IS NOT NULL AND TRIM(idempotency_key)<>''
 GROUP BY environment,direction,idempotency_key HAVING COUNT(*)>1
) x")->fetchColumn();
v617_check($checks, $failures, 'no duplicate production idempotency keys', $duplicateKeys === 0, 'groups=' . $duplicateKeys);

$orphanAllocations = (int)$pdo->query("SELECT COUNT(*) FROM billing_payment_allocations a LEFT JOIN billing_payments p ON p.id=a.payment_id LEFT JOIN billing_payment_batches b ON b.id=a.payment_batch_id LEFT JOIN billing_collections c ON c.id=a.collection_id WHERE p.id IS NULL OR b.id IS NULL OR c.id IS NULL")->fetchColumn();
v617_check($checks, $failures, 'no orphan payment allocations', $orphanAllocations === 0, 'rows=' . $orphanAllocations);

$invalidBalances = (int)$pdo->query("SELECT COUNT(*) FROM billing_collections WHERE BINARY environment=BINARY 'production' AND (paid_amount < -0.01 OR balance_amount < -0.01 OR ABS(total_amount-paid_amount-balance_amount)>0.02)")->fetchColumn();
v617_check($checks, $failures, 'production collection balances consistent', $invalidBalances === 0, 'rows=' . $invalidBalances);

$permissions = $pdo->query("SELECT
 (SELECT COUNT(*) FROM permissions WHERE BINARY permission_key=BINARY 'billing.receivables.bulk_collect') receivable_permission,
 (SELECT COUNT(*) FROM permissions WHERE BINARY permission_key=BINARY 'billing.payables.bulk_pay') payable_permission")->fetch(PDO::FETCH_ASSOC);
v617_check($checks, $failures, 'explicit bulk permissions exist', (int)$permissions['receivable_permission'] === 1 && (int)$permissions['payable_permission'] === 1, json_encode($permissions));

echo "ICYLAB BILLING CONTROLLED BULK PAYMENTS ACTIVATION DIAGNOSTIC v6.17\n";
echo 'Generated at UTC: ' . gmdate('Y-m-d H:i:s') . "\n";
echo 'PHP: ' . PHP_VERSION . "\n";
echo 'App root: ' . $root . "\n";
echo 'DB name: ' . $dbName . "\n\n";
foreach ($checks as $check) {
    echo $check[0] . ': ' . ($check[1] ? 'PASS' : 'FAIL') . ($check[2] !== '' ? ' [' . $check[2] . ']' : '') . "\n";
}
echo "\nRESULT: " . (count($failures) === 0 ? 'PASS' : 'FAIL') . "\n";
if ($failures) foreach ($failures as $failure) echo '- ' . $failure . "\n";
exit($failures ? 1 : 0);
