<?php
/**
 * ════════════════════════════════════════════════════════════════════
 * PropFirmRuleEngine golden-value regression, real logging.
 * ────────────────────────────────────────────────────────────────────
 * Runs PropFirmRuleEngine::compute() against every real prop-firm
 * account in the live database (every row in challenge_accounts) and
 * compares its output, field by field, against
 * AnalyticsEngine::getChallengeMetrics()'s current (already-fixed,
 * already-live-canonical) output for the same account.
 *
 * PropFirmRuleEngine is NOT wired to any consumer yet — this script's
 * only job is to prove its output is byte-for-byte equivalent to the
 * engine it is about to replace, before anything is allowed to read
 * from it. Per docs/architecture/SINGLE_SOURCE_OF_TRUTH_PROPOSAL.md
 * §4 and MIGRATION_PLAN.md's Wave 0.
 *
 * Usage: php scripts/verify_prop_firm_rule_engine.php
 * Output: prints a summary to stdout AND writes a full, persistent,
 * field-by-field log to docs/architecture/golden_value_logs/.
 * ════════════════════════════════════════════════════════════════════
 */

require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../api/analytics/engine.php';
require_once __DIR__ . '/../src/PropFirmRuleEngine.php';

$logDir = __DIR__ . '/../docs/architecture/golden_value_logs';
if (!is_dir($logDir)) {
    mkdir($logDir, 0777, true);
}
$timestamp = date('Y-m-d_His');
$logPath = $logDir . "/prop_firm_rule_engine_regression_{$timestamp}.log";
$logFh = fopen($logPath, 'w');

function logLine(string $s = ''): void {
    global $logFh;
    fwrite($logFh, $s . "\n");
    echo $s . "\n";
}

$pdo = new PDO(
    "mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=utf8mb4",
    DB_USER, DB_PASS,
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);

$accounts = $pdo->query("
    SELECT ca.account_id, ca.challenge_type, ca.current_stage,
           ta.lifecycle_state, ta.starting_balance, ta.label
    FROM challenge_accounts ca
    JOIN trading_accounts ta ON ca.account_id = ta.id
    ORDER BY ta.lifecycle_state, ca.challenge_type, ca.account_id
")->fetchAll(PDO::FETCH_ASSOC);

logLine("PropFirmRuleEngine golden-value regression");
logLine("Run at: " . date('Y-m-d H:i:s'));
logLine("Total real prop-firm accounts (challenge_accounts rows): " . count($accounts));
logLine(str_repeat('=', 100));
logLine();

// Fields to compare, expressed as dot-paths into both result arrays.
// Deliberately excludes overall_drawdown.max_drawdown_percentage,
// account_metrics, and challenge_data — those are NOT part of
// PropFirmRuleEngine's scope by design (peak-equity-relative drawdown
// is AnalyticsEngine::getAccountMetrics()'s stat, not this engine's;
// account_metrics/challenge_data are pass-through/raw-row fields the
// Proposal never assigned to PropFirmRuleEngine).
$fieldsToCompare = [
    'current_stage',
    'phase_balance',
    'phase.profit_target_pct',
    'phase.max_daily_loss_pct',
    'phase.max_overall_loss_pct',
    'distance_to_pass.amount',
    'distance_to_pass.percentage',
    'distance_to_pass.target_profit_amount',
    'distance_to_pass.current_profit_amount',
    'distance_to_pass.progress_percentage',
    'distance_to_pass.is_passed',
    'daily_drawdown.current_amount',
    'daily_drawdown.limit_amount',
    'daily_drawdown.remaining_amount',
    'daily_drawdown.current_percentage',
    'daily_drawdown.limit_percentage',
    'daily_drawdown.is_breached',
    'overall_drawdown.current_amount',
    'overall_drawdown.limit_amount',
    'overall_drawdown.remaining_amount',
    'overall_drawdown.breach_level_amount',
    'overall_drawdown.current_percentage',
    'overall_drawdown.limit_percentage',
    'distance_to_breach.amount',
    'distance_to_breach.percentage',
    'distance_to_breach.is_near_breach',
    'distance_to_breach.warning_level',
    'excess_amounts.excess_profit_after_pass',
    'excess_amounts.excess_loss_after_breach',
    'excess_amounts.is_breached',
    'excess_amounts.has_excess_profit',
];

function dget(array $arr, string $path) {
    $cur = $arr;
    foreach (explode('.', $path) as $seg) {
        if (!is_array($cur) || !array_key_exists($seg, $cur)) {
            return '__MISSING__';
        }
        $cur = $cur[$seg];
    }
    return $cur;
}

function valuesEqual($a, $b): bool {
    if ($a === '__MISSING__' || $b === '__MISSING__') {
        return $a === $b;
    }
    if (is_bool($a) || is_bool($b)) {
        return (bool)$a === (bool)$b;
    }
    if (is_numeric($a) && is_numeric($b)) {
        // Tolerate float rounding noise at the sub-cent/sub-basis-point level.
        return abs((float)$a - (float)$b) < 0.005;
    }
    return $a === $b;
}

$matched = 0;
$errored = 0;
$differed = 0;
$diffDetails = [];
$erroredDetails = [];
$allDetails = [];

foreach ($accounts as $acc) {
    $accountId = $acc['account_id'];
    $label = $acc['label'] ?? '(no label)';

    try {
        $engineResult = PropFirmRuleEngine::compute($accountId);
    } catch (\Throwable $e) {
        $errored++;
        $erroredDetails[] = "Account {$accountId} ({$label}): PropFirmRuleEngine::compute() THREW: " . $e->getMessage();
        continue;
    }

    try {
        $canonicalResult = AnalyticsEngine::getChallengeMetrics($accountId);
    } catch (\Throwable $e) {
        $errored++;
        $erroredDetails[] = "Account {$accountId} ({$label}): AnalyticsEngine::getChallengeMetrics() THREW: " . $e->getMessage();
        continue;
    }

    if (isset($engineResult['error']) || isset($canonicalResult['error'])) {
        $errored++;
        $erroredDetails[] = "Account {$accountId} ({$label}): error field present — engine="
            . json_encode($engineResult['error'] ?? null) . " canonical=" . json_encode($canonicalResult['error'] ?? null);
        continue;
    }

    // Unconditional, per-account, per-field record — every account gets
    // its full input/output pair logged regardless of outcome, so a
    // disagreement (or a question about how a specific account resolved)
    // is debuggable retroactively without having to re-run the script.
    // Not just a diff-only log: the whole point of a golden-value log is
    // that the "golden" values themselves are on record, not only the
    // cases that failed to match them.
    $accountDiffs = [];
    $fieldRows = [];
    foreach ($fieldsToCompare as $path) {
        $ev = dget($engineResult, $path);
        $cv = dget($canonicalResult, $path);
        $eq = valuesEqual($ev, $cv);
        $fieldRows[] = sprintf(
            "    %-45s engine=%-20s canonical=%-20s %s",
            $path,
            json_encode($ev),
            json_encode($cv),
            $eq ? '' : '  <-- DIFFERS'
        );
        if (!$eq) {
            $accountDiffs[] = end($fieldRows);
        }
    }

    $allDetails[] = [
        'account_id' => $accountId,
        'label' => $label,
        'lifecycle_state' => $acc['lifecycle_state'],
        'challenge_type' => $acc['challenge_type'],
        'current_stage_raw' => $acc['current_stage'],
        'starting_balance' => $acc['starting_balance'],
        'field_rows' => $fieldRows,
        'has_diff' => !empty($accountDiffs),
    ];

    if (empty($accountDiffs)) {
        $matched++;
    } else {
        $differed++;
        $diffDetails[] = [
            'account_id' => $accountId,
            'label' => $label,
            'lifecycle_state' => $acc['lifecycle_state'],
            'challenge_type' => $acc['challenge_type'],
            'current_stage_raw' => $acc['current_stage'],
            'diffs' => $accountDiffs,
        ];
    }
}

logLine("RESULTS");
logLine(str_repeat('-', 100));
logLine("Matched (all " . count($fieldsToCompare) . " compared fields identical): {$matched}");
logLine("Differed (at least one field diverged):                  {$differed}");
logLine("Errored (exception or ['error'=>...] from either side):  {$errored}");
logLine("Total accounts checked:                                  " . count($accounts));
logLine();

if (!empty($erroredDetails)) {
    logLine("ERRORED ACCOUNTS — FULL DETAIL");
    logLine(str_repeat('-', 100));
    foreach ($erroredDetails as $d) {
        logLine($d);
    }
    logLine();
}

if (!empty($diffDetails)) {
    logLine("DIFFERED ACCOUNTS — FULL FIELD-LEVEL DETAIL");
    logLine(str_repeat('-', 100));
    foreach ($diffDetails as $d) {
        logLine(sprintf(
            "Account %s (%s) — lifecycle_state=%s challenge_type=%s current_stage(raw col)=%s",
            $d['account_id'], $d['label'], $d['lifecycle_state'], $d['challenge_type'], $d['current_stage_raw'] ?? 'NULL'
        ));
        foreach ($d['diffs'] as $line) {
            logLine($line);
        }
        logLine();
    }
} else {
    logLine("Zero field-level differences found across all accounts that computed cleanly.");
}
logLine();

logLine("PER-ACCOUNT FULL DETAIL — every account, every compared field, logged unconditionally");
logLine("(not diff-only — the golden values themselves are on record here, not just failures)");
logLine(str_repeat('-', 100));
foreach ($allDetails as $d) {
    logLine(sprintf(
        "Account %s (%s) — lifecycle_state=%s challenge_type=%s current_stage(raw col)=%s starting_balance=%s [%s]",
        $d['account_id'], $d['label'], $d['lifecycle_state'], $d['challenge_type'],
        $d['current_stage_raw'] ?? 'NULL', $d['starting_balance'],
        $d['has_diff'] ? 'DIFFERED' : 'MATCHED'
    ));
    foreach ($d['field_rows'] as $line) {
        logLine($line);
    }
    logLine();
}

logLine(str_repeat('=', 100));
logLine("Log written to: " . $logPath);

fclose($logFh);

echo "\n--- DONE ---\n";
echo "Log file: {$logPath}\n";
