<?php
// /traderalign/api/calculator/compute.php
//
// POST /api/calculator/compute
//
// The new, authoritative server-side entry point for the Calculator's
// business logic — see docs/architecture/CALCULATOR_BUSINESS_LOGIC_PHP_
// MIGRATION.md and CLAUDE.md's dated migration session entry for the
// full background. This endpoint is a thin HTTP wrapper around
// src/CalculatorEngine.php: it does no math of its own, only input
// parsing/validation and response shaping. All real computation lives
// in CalculatorEngine, which has been verified against the real,
// unmodified calculator-source.html JS (60 synthetic scenarios, 0
// mismatches) and against every real historical calculator-sourced
// trade in the database that could be verified (see the migration
// report for the full breakdown).
//
// Request body (JSON):
//   {
//     "risk_mode": "auto"|"fixed"|"manual"|"rr",
//     "account_size": number, "current_balance": number,
//     "instrument": "eurusd", "entry_price": number, "sl_price": number,
//     "tp_prices": [number, ...],
//     "atr_value": number|null,
//     "fixed_risk_amount": number|null,   // risk_mode=fixed
//     "manual_lot_size": number|null,     // risk_mode=manual
//     "rr_risk_pct": number|null,         // risk_mode=rr
//     "broker_tick_size": number|null, "broker_contract_size": number|null,
//     "index_tick_value": number|null,
//     "live_rates": { "live_jpy": 156.16, ... },  // already-resolved,
//         per the migration's live-rate decision: the frontend still
//         fetches/resolves live FX rates client-side exactly as before;
//         this endpoint receives the resolved numbers as plain input,
//         not proprietary logic.
//     "prop_firm": { "active": bool, "phase": int, "funded": bool,
//                    "target": number|null, "maxdd": number|null, "daily": number|null },
//     "planned_timeframe": string|null, "session": string|null, "session_tag": string|null,
//     "mood_before": int|null, "energy_before": int|null, "confidence_before": int|null,
//     "mode_at_capture": "advanced"|"minimal", "calculator_session_id": string|null,
//     "account_size_at_entry": number|null, "balance_at_entry": number|null,
//     "leverage": string|null
//   }
//
// Response (200):
//   { "success": true, "mode": "...", "warnings": [...], "show_blocker": bool,
//     "block_reason": "..."|null, "trade_object": {...}, "display": {...} }
//
// Errors:
//   400 { "success": false, "error": "..." }  — bad/missing input, unknown instrument
//   401 (via $auth->requireAuth())
//   405 { "success": false, "error": "Method not allowed" }
//   500 { "success": false, "error": "..." }  — unexpected failure

require_once __DIR__ . '/../../src/CalculatorEngine.php';

header('Content-Type: application/json');

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['success' => false, 'error' => 'Method not allowed']);
    exit;
}

$userId = $auth->requireAuth();

$input = json_decode(file_get_contents('php://input'), true);
if (!is_array($input)) {
    http_response_code(400);
    echo json_encode(['success' => false, 'error' => 'Invalid JSON body']);
    exit;
}

$riskMode = $input['risk_mode'] ?? 'auto';
if (!in_array($riskMode, ['auto', 'fixed', 'manual', 'rr'], true)) {
    http_response_code(400);
    echo json_encode(['success' => false, 'error' => 'risk_mode must be one of auto, fixed, manual, rr']);
    exit;
}

$requiredNumeric = ['account_size', 'entry_price', 'sl_price'];
foreach ($requiredNumeric as $field) {
    if (!isset($input[$field]) || !is_numeric($input[$field])) {
        http_response_code(400);
        echo json_encode(['success' => false, 'error' => "Missing or non-numeric required field: {$field}"]);
        exit;
    }
}
if (empty($input['instrument']) || !is_string($input['instrument'])) {
    http_response_code(400);
    echo json_encode(['success' => false, 'error' => 'Missing instrument']);
    exit;
}
if (empty($input['tp_prices']) || !is_array($input['tp_prices'])) {
    http_response_code(400);
    echo json_encode(['success' => false, 'error' => 'At least one tp_prices entry is required']);
    exit;
}

try {
    if ($riskMode === 'rr') {
        $out = CalculatorEngine::computeRRMode($input);
    } else {
        // computeStandardMode() reads risk_mode itself (auto/fixed/manual)
        // from $input — pass it straight through unchanged.
        $out = CalculatorEngine::computeStandardMode($input);
    }
} catch (\InvalidArgumentException $e) {
    http_response_code(400);
    echo json_encode(['success' => false, 'error' => $e->getMessage()]);
    exit;
} catch (\Throwable $e) {
    error_log('[calculator/compute] ' . $e->getMessage() . "\n" . $e->getTraceAsString());
    http_response_code(500);
    echo json_encode(['success' => false, 'error' => 'Calculation failed. Please check your inputs and try again.']);
    exit;
}

// 2026-08-21 IP-exposure fix: RR mode's own $out['display'] used to carry
// the raw per-instrument pip_multiplier/min_lot/step_decimals constants
// straight through to the client, which used them to compute a handful of
// final on-screen numbers (SL/TP distance in points, the rounded lot
// display, the min-lot risk note) itself. A logged-in user with dev tools
// could enumerate all ~56 instruments one authenticated request at a time
// and reconstruct those tuned constants for the whole table. This does
// the exact same arithmetic here instead — using values CalculatorEngine
// already returned (untouched, not modified by this fix) — and sends only
// the finished, display-ready numbers. The on-screen result is unchanged;
// only where the multiplication happens moved from the browser to here.
if ($riskMode === 'rr' && isset($out['display']['pip_multiplier'])) {
    $pipMultiplier = (float)$out['display']['pip_multiplier'];
    $minLot        = (float)($out['display']['min_lot'] ?? 0);
    $stepDecimals  = (int)($out['display']['step_decimals'] ?? 2);
    $lots          = (float)($out['display']['lots'] ?? 0);
    $riskPerPos    = (float)($out['display']['risk_per_position'] ?? 0);

    $tradeObj = $out['trade_object'] ?? [];
    $entryPrice = (float)($tradeObj['entry_price'] ?? 0);
    $slPrice    = (float)($tradeObj['sl_price'] ?? 0);

    $out['display']['sl_points'] = abs($entryPrice - $slPrice) * $pipMultiplier;

    // Same tp1_price..tp10_price, non-null, in-order walk the client's own
    // adapter code already does when it builds tpPrices2 from trade_object.
    $tpPoints = [];
    for ($i = 1; $i <= 10; $i++) {
        $tp = $tradeObj["tp{$i}_price"] ?? null;
        if ($tp === null) {
            continue;
        }
        $tpPoints[] = abs((float)$tp - $entryPrice) * $pipMultiplier;
    }
    $out['display']['tp_points'] = $tpPoints;

    $out['display']['min_lot_display']  = number_format($minLot, $stepDecimals, '.', '');
    $out['display']['lots_display']     = number_format($lots, $stepDecimals, '.', '');
    $out['display']['min_lot_risk_usd'] = $lots != 0.0 ? ($minLot * $riskPerPos) / $lots : 0.0;

    // The raw tuned constants themselves never leave the server.
    unset(
        $out['display']['pip_multiplier'],
        $out['display']['min_lot'],
        $out['display']['step_decimals']
    );
}

echo json_encode(array_merge(['success' => true], $out));
