<?php
// =========================
// MRDCA Bot API (Model B - high-water mark se zámkem na safe)
// =========================
//
// Endpointy:
//   type=getStartDate
//     params: account_number
//     response: JSON {"start_date":"YYYY-MM-DD HH:MM:SS"} (UTC) | {"start_date":null}
//
//   type=report_profit
//     params: account_number, current_profit (může být záporné)
//     response: plaintext "ok" | "error: <reason>"
//     Atomicita: jeden UPDATE statement, GREATEST se vyhodnotí na úrovni řádku
//                pod row-level lockem InnoDB.
//
//   type=getPoolSettings
//     params: account_number
//     response: JSON s pool_total + weight_distribution + Model B diagnostikou
//
// Vzorce Model B (2026-07-17: pool_manual = offset reportovaného profitu na
// řádce poolu; smí být i ZÁPORNÝ — vklad na wallet = +X plně nasaditelné do
// poolu (safe se z vkladu neskimuje, není to zisk), výběr/ruční škrcení = −X
// řeže pool dolar za dolar, podlaha 0; HWM/safe zůstávají čistě ze zisku):
//   max_profit = MAX(stored_max_profit, current_profit)
//   safe       = max_profit * safe_pct
//   pool       = MAX(0, current_profit + pool_manual - safe)
//   pool_total = pool          (pool_manual je už UVNITŘ vzorce, nepřičítá se podruhé)

require_once __DIR__ . '/db.php';

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
    http_response_code(500);
    die("error: db connection failed");
}

$type          = $_REQUEST['type'] ?? '';
$accountNumber = $_REQUEST['account_number'] ?? '';

// --- Request/response log (mrdca_api_log) --------------------------------
// Endpoint byl dřív black-box: report_profit/getPoolSettings/getStartDate se
// nikde nezaznamenávaly, takže výpadek reportingu nešel zpětně diagnostikovat.
// Output-buffer + shutdown zachytí KAŽDOU cestu (i chybové die()) s tělem
// odpovědi a HTTP kódem. Ring buffer — ořezává cleanup.php.
$__api_t0  = microtime(true);
$__api_req = isset($_REQUEST['current_profit']) ? ('current_profit=' . $_REQUEST['current_profit']) : '';
ob_start();
register_shutdown_function(function () use ($pdo, $type, $accountNumber, $__api_req, $__api_t0) {
    $resp = ob_get_contents();
    $http = http_response_code() ?: 200;
    $ip   = $_SERVER['REMOTE_ADDR'] ?? '';
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) $ip = trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
    try {
        $pdo->prepare("INSERT INTO mrdca_api_log
              (account_number, type, request, http_status, response, ip, duration_ms)
              VALUES (?,?,?,?,?,?,?)")
            ->execute([
                mb_substr((string)$accountNumber, 0, 32),
                mb_substr((string)$type, 0, 32),
                mb_substr((string)$__api_req, 0, 255),
                (int)$http,
                mb_substr((string)$resp, 0, 512),
                mb_substr((string)$ip, 0, 64),
                (int)round((microtime(true) - $__api_t0) * 1000),
            ]);
        // Probabilistický prune (~1/500 requestů) — drží tabulku pod ~6000 řádky
        // i mezi běhy cleanup.php, bez DELETE na každý request.
        if (random_int(1, 500) === 1) {
            $cut = $pdo->query("SELECT id FROM mrdca_api_log ORDER BY id DESC LIMIT 1 OFFSET 5999")->fetchColumn();
            if ($cut) $pdo->prepare("DELETE FROM mrdca_api_log WHERE id < ?")->execute([$cut]);
        }
    } catch (Throwable $e) {
        error_log('mrdca_api_log insert failed: ' . $e->getMessage());
    }
});

// =========================================================================
// getStartDate
// =========================================================================
if ($type === 'getStartDate') {
    if ($accountNumber === '') {
        http_response_code(400);
        die("error: missing account_number");
    }

    try {
        $stmt = $pdo->prepare("SELECT start_date FROM bot_mrdca WHERE account_number = ?");
        $stmt->execute([$accountNumber]);
        $startDate = $stmt->fetchColumn();
    } catch (PDOException $e) {
        http_response_code(500);
        error_log('mrdca_api getStartDate: ' . $e->getMessage());
        die("error: db error");
    }

    header('Content-Type: application/json');
    // false (account neexistuje) i NULL (sloupec NULL) → null v JSON
    echo json_encode(['start_date' => ($startDate === false ? null : $startDate)]);
    exit;
}

// =========================================================================
// report_profit
// =========================================================================
if ($type === 'report_profit') {
    if ($accountNumber === '') {
        http_response_code(400);
        die("error: missing account_number");
    }

    $rawProfit = $_REQUEST['current_profit'] ?? '';
    if ($rawProfit === '' || !is_numeric($rawProfit)) {
        http_response_code(400);
        die("error: invalid number");
    }
    $cp = floatval($rawProfit);

    try {
        // Existence check (proti race s mazáním účtu - levné, jednou SELECT)
        $exists = $pdo->prepare("SELECT 1 FROM bot_mrdca WHERE account_number = ?");
        $exists->execute([$accountNumber]);
        if (!$exists->fetchColumn()) {
            // 200 + plaintext: bot to ignoruje, dál pingá. Admin musí účet
            // založit v dashboardu, jinak Model B counters nikdy nestartují.
            die("error: unknown account");
        }

        // Atomicky v jednom UPDATE - GREATEST nad původní hodnotou max_profit
        // (každý GREATEST vidí stejný řádek pod row-level lockem InnoDB).
        // pool_manual se v tomhle UPDATE NEpřiřazuje → MySQL left-to-right
        // sémantika čte jeho původní (aktuální) hodnotu; max_profit se záměrně
        // re-derivuje přes GREATEST(...) místo odkazu na čerstvě přiřazený sloupec.
        $stmt = $pdo->prepare("
            UPDATE bot_mrdca SET
              current_profit = :cp1,
              max_profit     = GREATEST(max_profit, :cp2),
              safe           = GREATEST(max_profit, :cp3) * safe_pct,
              pool           = GREATEST(0, :cp4 + pool_manual - GREATEST(max_profit, :cp5) * safe_pct),
              last_report_at = UTC_TIMESTAMP()
            WHERE account_number = :acc
        ");
        $stmt->execute([
            'cp1' => $cp, 'cp2' => $cp, 'cp3' => $cp, 'cp4' => $cp, 'cp5' => $cp,
            'acc' => $accountNumber,
        ]);
    } catch (PDOException $e) {
        http_response_code(500);
        error_log('mrdca_api report_profit: ' . $e->getMessage());
        die("error: db error");
    }

    echo "ok";
    exit;
}

// =========================================================================
// getPoolSettings
// =========================================================================
if ($type === 'getPoolSettings') {
    if ($accountNumber === '') {
        http_response_code(400);
        die("error: missing account_number");
    }

    try {
        $stmt = $pdo->prepare("
            SELECT account_number, slots, safe_pct, pool, pool_manual, weight_distribution,
                   safe, current_profit, max_profit, last_report_at
              FROM bot_mrdca
             WHERE account_number = ?
        ");
        $stmt->execute([$accountNumber]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
    } catch (PDOException $e) {
        http_response_code(500);
        error_log('mrdca_api getPoolSettings: ' . $e->getMessage());
        die("error: db error");
    }

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

    if ($row) {
        // Bot kontroluje `slots > 0` jako sanity flag (= účet je zkonfigurovaný).
        // `safePrc` posíláme dál pro kompatibilitu s ApiClient.cs Print logem -
        // server `safe` je nově hotová absolutní hodnota, ale bot starou verzi
        // proměnné v třídě má a deserializuje ji.
        // pool_total se počítá ČERSTVĚ ze counterů (ne ze stored `pool`):
        // (a) pool_manual je od 2026-07-17 součástí vzorce — stored pool z doby
        //     před deployem/před posledním pingem by ho neobsahoval,
        // (b) změna pool_manual v dashboardu se tak projeví okamžitě, bez čekání
        //     na další report_profit ping Reporter bota.
        $pool       = floatval($row['pool']);
        $poolManual = floatval($row['pool_manual']);
        $safePctVal = floatval($row['safe_pct']);
        $poolTotal  = max(0.0, floatval($row['current_profit']) + $poolManual - floatval($row['safe']));
        echo json_encode([
            'account_number'      => $row['account_number'],
            'slots'               => intval($row['slots']),
            'safePrc'             => intval(round($safePctVal * 100)),
            'pool'                => $pool,
            'pool_manual'         => $poolManual,
            'pool_total'          => $poolTotal,
            'weight_distribution' => $row['weight_distribution'],
            'safe'                => floatval($row['safe']),
            'safe_pct'            => $safePctVal,
            'current_profit'      => floatval($row['current_profit']),
            'max_profit'          => floatval($row['max_profit']),
            'last_report_at'      => $row['last_report_at'],
        ]);
    } else {
        // Account neznámý - bot dostane defaults se slots=0, takže zůstane v "not configured" módu.
        echo json_encode([
            'account_number'      => '0',
            'slots'               => 0,
            'safePrc'             => 0,
            'pool'                => 0.0,
            'pool_manual'         => 0.0,
            'pool_total'          => 0.0,
            'weight_distribution' => '25,25,25,25',
            'safe'                => 0.0,
            'safe_pct'            => 0.0,
            'current_profit'      => 0.0,
            'max_profit'          => 0.0,
            'last_report_at'      => null,
        ]);
    }
    exit;
}

// =========================================================================
// getBotHealth — tick-starvation telemetrie (MRDCA 2.0.2+ health soubory)
// Per-label: stáří posledního ticku + market_open. Dashboard/monitor tak
// rozliší "proces žije" (strategy_ping) od "obchodní smyčka žije" (tady).
// stale = market_open=1 a last_tick_utc starší než 15 min (nebo chybí),
// posuzováno vůči utc_now z bota (stejné hodinky jako last_tick_utc).
// =========================================================================
if ($type === 'getBotHealth') {
    if ($accountNumber === '') {
        http_response_code(400);
        die("error: missing account_number");
    }

    try {
        $stmt = $pdo->prepare("
            SELECT label, utc_now, server_time, last_tick_utc, market_open, bot_version, updated_at,
                   TIMESTAMPDIFF(MINUTE, updated_at, UTC_TIMESTAMP()) AS updated_mins_ago,
                   CASE WHEN last_tick_utc IS NULL THEN NULL
                        ELSE TIMESTAMPDIFF(MINUTE, last_tick_utc, utc_now) END AS tick_age_min
              FROM strategy_health
             WHERE account_number = ?
             ORDER BY label
        ");
        $stmt->execute([$accountNumber]);
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (PDOException $e) {
        http_response_code(500);
        error_log('mrdca_api getBotHealth: ' . $e->getMessage());
        die("error: db error");
    }

    header('Content-Type: application/json');
    $out = [];
    foreach ($rows as $r) {
        $tickAge = $r['tick_age_min'] !== null ? intval($r['tick_age_min']) : null;
        $stale = intval($r['market_open']) === 1 && ($tickAge === null || $tickAge > 15);
        $out[] = [
            'label'            => $r['label'],
            'bot_version'      => $r['bot_version'],
            'market_open'      => intval($r['market_open']),
            'last_tick_utc'    => $r['last_tick_utc'],
            'tick_age_min'     => $tickAge,
            'utc_now'          => $r['utc_now'],
            'server_time'      => $r['server_time'],
            'updated_at'       => $r['updated_at'],
            'updated_mins_ago' => $r['updated_mins_ago'] !== null ? intval($r['updated_mins_ago']) : null,
            'stale'            => $stale,
        ];
    }
    echo json_encode(['account_number' => $accountNumber, 'health' => $out]);
    exit;
}

http_response_code(400);
die("error: unknown type. Supported: getStartDate, report_profit, getPoolSettings, getBotHealth");
