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

/**
 * Return the source body of a named method using PHP tokens.
 * This correctly handles nested parentheses in signatures such as:
 *   function listReceivables(array $filters = array())
 */
function icylab_method_body_from_tokens($path, $method)
{
    $src = @file_get_contents($path);
    if ($src === false) {
        return null;
    }

    $tokens = token_get_all($src);
    $count = count($tokens);

    for ($i = 0; $i < $count; $i++) {
        $token = $tokens[$i];
        if (!is_array($token) || $token[0] !== T_FUNCTION) {
            continue;
        }

        $j = $i + 1;

        // Skip whitespace/comments and an optional reference marker (&).
        while ($j < $count) {
            $candidate = $tokens[$j];
            if (is_array($candidate) && in_array($candidate[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT), true)) {
                $j++;
                continue;
            }
            if ($candidate === '&') {
                $j++;
                continue;
            }
            break;
        }

        if ($j >= $count || !is_array($tokens[$j]) || $tokens[$j][0] !== T_STRING) {
            continue; // Anonymous function or invalid declaration.
        }

        if (strcasecmp($tokens[$j][1], $method) !== 0) {
            continue;
        }

        // Find the opening brace of the method body. Tokenization prevents
        // parentheses inside default values from confusing this search.
        while ($j < $count && $tokens[$j] !== '{') {
            $j++;
        }

        if ($j >= $count || $tokens[$j] !== '{') {
            return null;
        }

        $depth = 1;
        $body = '';
        for ($j = $j + 1; $j < $count; $j++) {
            $part = $tokens[$j];
            $text = is_array($part) ? $part[1] : $part;

            if ($part === '{') {
                $depth++;
            } elseif ($part === '}') {
                $depth--;
                if ($depth === 0) {
                    return $body;
                }
            }

            $body .= $text;
        }

        return null;
    }

    return null;
}

$root = dirname(__DIR__);
$checks = array(
    array(
        'services/billing/BillingReceivablesService.php',
        'listReceivables',
        array('refreshCalculatedReceivableStatuses(', 'ensureCollectionsForEnvironment(', 'resolveCustomers('),
        "billing_assert_runtime_operation('read'"
    ),
    array(
        'services/billing/BillingReceivablesService.php',
        'getDocument',
        array('ensureDocumentCustomerAndCollection(', 'refreshCalculatedReceivableStatuses('),
        "billing_assert_runtime_operation('read'"
    ),
    array(
        'services/billing/BillingPayablesService.php',
        'listPayables',
        array('resolveSupplierDefaults(', 'refreshCalculatedPayableStatuses(', 'refreshOverdue('),
        "billing_assert_runtime_operation('read'"
    ),
    array(
        'services/billing/BillingPayablesService.php',
        'getKpis',
        array('resolveSupplierDefaults(', 'refreshCalculatedPayableStatuses(', 'refreshOverdue('),
        "billing_assert_runtime_operation('read'"
    ),
    array(
        'services/billing/BillingPayablesService.php',
        'getDocument',
        array('resolveSupplierDefaults(', 'refreshCalculatedPayableStatuses(', 'refreshOverdue(', 'ensurePayableCollection('),
        "billing_assert_runtime_operation('read'"
    ),
    array(
        'services/billing/BillingSupplierAgingService.php',
        'summary',
        array('refreshBaseState('),
        "billing_assert_runtime_operation('read'"
    ),
    array(
        'services/billing/BillingSupplierAgingService.php',
        'detail',
        array('refreshBaseState('),
        "billing_assert_runtime_operation('read'"
    ),
    array(
        'services/billing/BillingPayablesService.php',
        'refreshAgingState',
        array(),
        "billing_assert_runtime_operation('finance'"
    ),
);

$failures = 0;

echo "ICYLAB BILLING FINANCE READ ISOLATION DIAGNOSTIC v6.12.1\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: " . (defined('ICYLAB_DB_NAME') ? ICYLAB_DB_NAME : '(undefined)') . "\n";
echo "Default FACTO environment: " . (defined('ICYLAB_FACTO_ENVIRONMENT') ? ICYLAB_FACTO_ENVIRONMENT : '(undefined)') . "\n\n";

foreach ($checks as $check) {
    list($rel, $method, $banned, $requiredToken) = $check;
    $body = icylab_method_body_from_tokens($root . '/' . $rel, $method);

    if ($body === null) {
        echo "{$rel}::{$method}: FAIL method not found\n";
        $failures++;
        continue;
    }

    $found = array();
    foreach ($banned as $token) {
        if (strpos($body, $token) !== false) {
            $found[] = $token;
        }
    }

    if ($found) {
        echo "{$rel}::{$method}: FAIL banned=" . implode(',', $found) . "\n";
        $failures++;
        continue;
    }

    if ($requiredToken !== '' && strpos($body, $requiredToken) === false) {
        echo "{$rel}::{$method}: FAIL required gate not found\n";
        $failures++;
        continue;
    }

    if ($method === 'refreshAgingState') {
        echo "{$rel}::{$method}: PASS explicit-finance-only\n";
    } else {
        echo "{$rel}::{$method}: PASS side-effect-free\n";
    }
}

$payablesPath = $root . '/services/billing/BillingPayablesService.php';
$payables = @file_get_contents($payablesPath);
if (
    $payables !== false
    && strpos($payables, 'effectivePayableStatusSql') !== false
    && strpos($payables, 'deriveEffectivePayableStatus') !== false
) {
    echo "payables_effective_status_projection: PASS\n";
} else {
    echo "payables_effective_status_projection: FAIL\n";
    $failures++;
}

echo "\nRUNTIME GATES\n";
$expected = array(
    'read' => true,
    'finance' => false,
    'issue' => false,
);
foreach ($expected as $op => $expectedAllowed) {
    $allowed = billing_runtime_gate_allows($op, 'production');
    $actualLabel = $allowed ? 'ALLOWED' : 'BLOCKED';
    $expectedLabel = $expectedAllowed ? 'ALLOWED' : 'BLOCKED';
    echo "production/{$op}: {$actualLabel} expected={$expectedLabel}\n";
    if ($allowed !== $expectedAllowed) {
        $failures++;
    }
}

echo "\nRESULT: " . ($failures === 0 ? 'PASS' : 'FAIL') . "\n";
exit($failures === 0 ? 0 : 1);
