<?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 v615_check(&$checks, &$failures, $label, $ok, $detail = '') {
    $checks[] = array($label, (bool)$ok, $detail);
    if (!$ok) $failures[] = $label . ($detail !== '' ? ': ' . $detail : '');
}
function v615_file($root, $relative) {
    $path = $root . '/' . $relative;
    return is_file($path) ? (string)file_get_contents($path) : '';
}
function v615_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 = v615_file($root, 'services/billing/BillingReceivablesService.php');
$pay = v615_file($root, 'services/billing/BillingPayablesService.php');
$recvEndpoint = v615_file($root, 'modules/billing/receivable_register_payment_ajax.php');
$payEndpoint = v615_file($root, 'modules/billing/payable_register_payment_ajax.php');
$recvUi = v615_file($root, 'modules/billing/receivables.js');
$payUi = v615_file($root, 'modules/billing/payables.php');
$customerAging = v615_file($root, 'modules/billing/customer_aging.js');
$supplierAging = v615_file($root, 'modules/billing/suppliers_aging.js');

v615_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();
v615_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();
v615_check($checks, $failures, 'unique idempotency index', $indexRows >= 1, 'rows=' . $indexRows);

v615_check($checks, $failures, 'receivable existing collection required',
    strpos($recv, 'getCollectionByDocumentId((string)$doc[\'document_id\']') !== false
    && strpos(substr($recv, strpos($recv, 'public function registerPayment'), 900), 'ensureReceivableCollection') === false);
v615_check($checks, $failures, 'receivable stale balance guard', v615_has_all($recv, array('expected_balance', 'lockAndRefreshCollection', 'abs($balance - $expectedBalance) > 0.01')));
v615_check($checks, $failures, 'receivable idempotency guard', v615_has_all($recv, array('idempotency_key', 'findPaymentReplay', 'assertPaymentReferenceAvailable')));
v615_check($checks, $failures, 'receivable production bulk blocked', strpos($recv, 'Los cobros masivos productivos permanecen deshabilitados') !== false);

v615_check($checks, $failures, 'payable existing collection required', strpos($pay, 'getPayableCollectionByInboxId((string)$doc[\'inbox_document_id\']') !== false);
v615_check($checks, $failures, 'payable row lock and balance guard', v615_has_all($pay, array('lockAndRefreshPayableCollection', 'FOR UPDATE', 'abs($balance - $expectedBalance) > 0.01')));
v615_check($checks, $failures, 'payable idempotency guard', v615_has_all($pay, array('idempotency_key', 'findPaymentReplay', 'assertPaymentReferenceAvailable')));
v615_check($checks, $failures, 'payable production bulk blocked', strpos($pay, 'Los pagos masivos productivos permanecen deshabilitados') !== false);

$forwardFields = array('expected_collection_id', 'expected_balance', 'idempotency_key');
v615_check($checks, $failures, 'receivable endpoint forwards guards', v615_has_all($recvEndpoint, array_merge($forwardFields, array('expected_document_id'))));
v615_check($checks, $failures, 'payable endpoint forwards guards', v615_has_all($payEndpoint, array_merge($forwardFields, array('expected_inbox_document_id'))));

$uiNeedles = array('idempotency_key', 'expected_collection_id', 'expected_balance', 'window.confirm');
v615_check($checks, $failures, 'receivables UI guarded', v615_has_all($recvUi, array_merge($uiNeedles, array('expected_document_id'))));
v615_check($checks, $failures, 'payables UI guarded', v615_has_all($payUi, array_merge($uiNeedles, array('expected_inbox_document_id'))));
v615_check($checks, $failures, 'customer aging UI guarded', v615_has_all($customerAging, array_merge($uiNeedles, array('expected_document_id'))));
v615_check($checks, $failures, 'supplier aging UI guarded', v615_has_all($supplierAging, array_merge($uiNeedles, array('expected_inbox_document_id'))));

$readAllowed = $financeBlocked = $issueBlocked = false;
try { billing_assert_runtime_operation('read', 'production', 'v6.15 diagnostic'); $readAllowed = true; } catch (Throwable $e) {}
try { billing_assert_runtime_operation('finance', 'production', 'v6.15 diagnostic'); } catch (Throwable $e) { $financeBlocked = true; }
try { billing_assert_runtime_operation('issue', 'production', 'v6.15 diagnostic'); } catch (Throwable $e) { $issueBlocked = true; }
v615_check($checks, $failures, 'production read allowed', $readAllowed);
v615_check($checks, $failures, 'production finance blocked before pilot', $financeBlocked);
v615_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);
v615_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'] === 110,
    json_encode($inventory));

$keyed = (int)$pdo->query("SELECT COUNT(*) FROM billing_payment_batches WHERE BINARY environment=BINARY 'production' AND idempotency_key IS NOT NULL AND TRIM(idempotency_key)<>''")->fetchColumn();
v615_check($checks, $failures, 'no keyed production payment before pilot', $keyed === 0, 'rows=' . $keyed);

$options = $pdo->query("SELECT
 (SELECT COUNT(*) FROM billing_payment_methods WHERE is_active=1) methods,
 (SELECT COUNT(*) FROM billing_cash_accounts WHERE is_active=1) accounts")->fetch(PDO::FETCH_ASSOC);
v615_check($checks, $failures, 'payment options active', (int)$options['methods'] > 0 && (int)$options['accounts'] > 0, json_encode($options));

echo "ICYLAB BILLING PAYMENT WRITE SAFETY DIAGNOSTIC v6.15\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);
