<?php
// STORAGE/TRANSACTIONS — атомарное закрытие + удаление состояния entity
require_once __DIR__ . '/db.php';

// ---------------------------------------------------------------
// Транзакция: закрытие позиции + обновление баланса (атомарно)
// ---------------------------------------------------------------

/**
 * Атомарно:
 *   1. INSERT в trades_closed
 *   2. UPSERT в wallet_balances
 *   3. DELETE позиции из open_positions
 *   4. INSERT новой позиции (если передана)
 *
 * Заменяет отдельные вызовы storage_insert_closed_trade() + save_balance() + save_positions()
 * при закрытии сделки — исключает рассинхрон при падении посередине.
 *
 * @param array       $trade          данные для trades_closed
 * @param string      $entityType     'wallet' | 'pool'
 * @param string      $entityId       wallet_id | pool_id
 * @param array       $newBalanceState уже пересчитанный баланс
 * @param string      $symbol         закрываемый символ
 * @param array|null  $newPosition    если не null — сразу открываем новую позицию (реверс)
 */
function storage_close_trade_atomic(
    array  $trade,
    string $entityType,
    string $entityId,
    array  $newBalanceState,
    string $symbol,
    ?array $newPosition = null
): void {
    $db = storage_db();
    storage_retry(function() use ($db, $trade, $entityType, $entityId, $newBalanceState, $symbol, $newPosition) {
    $db->beginTransaction();
    try {
        // 1. INSERT закрытой сделки (общий хелпер — тот же список колонок)
        storage_insert_trade_row($db, $trade);

        // 2. UPSERT баланса (общий хелпер)
        // Гарантируем что баланс не уходит в отрицательную зону
        $newBalanceState['balance'] = max(0, $newBalanceState['balance']);
        storage_upsert_balance_row($db, $entityType, $entityId, $newBalanceState);

        // 3. Удаляем закрытую позицию
        $db->prepare('DELETE FROM open_positions WHERE entity_type = ? AND entity_id = ? AND symbol = ?')
           ->execute([$entityType, $entityId, $symbol]);

        // 4. Открываем новую позицию (реверс) — если передана
        if ($newPosition !== null) {
            $db->prepare('
                INSERT INTO open_positions
                    (entity_type, entity_id, symbol, side, entry_price, trader_entry_price,
                     size_usd, leverage, entry_fee, opened_at, trade_id, entry_reason,
                     owner_trader_id, owner_trader_label, trader_size_coins, symbol_exchange)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ')->execute([
                $entityType, $entityId, $symbol,
                $newPosition['side'],
                (float)  ($newPosition['entry_price']        ?? 0),
                isset($newPosition['trader_entry_price']) && $newPosition['trader_entry_price'] !== null
                    ? (float) $newPosition['trader_entry_price'] : null,
                (float)  ($newPosition['size_usd']           ?? 0),
                (int)    ($newPosition['leverage']           ?? 1),
                (float)  ($newPosition['entry_fee']          ?? 0),
                (int)    ($newPosition['opened_at']          ?? 0),
                $newPosition['trade_id']     ?? '',
                $newPosition['entry_reason'] ?? 'normal',
                $newPosition['owner_trader_id']    ?? null,
                $newPosition['owner_trader_label'] ?? null,
                isset($newPosition['trader_size_coins']) && $newPosition['trader_size_coins'] !== null
                    ? (float) $newPosition['trader_size_coins'] : null,
                $newPosition['symbol_exchange']    ?? null,
            ]);
        }

        $db->commit();
    } catch (Throwable $e) {
        $db->rollBack();
        throw $e;
    }
    }); // storage_retry
}

// ---------------------------------------------------------------
// Полное удаление состояния entity
// ---------------------------------------------------------------

/**
 * Удаляет ВСЁ оперативное состояние entity: баланс, открытые позиции,
 * engine-state (watermark+cooldown), seen_fills и watching.
 * Историю в trades_closed/signals НЕ трогает — она остаётся для аналитики.
 *
 * Заменяет старые @unlink(wallet_*_file()) в dashboard, которые после
 * перехода на SQLite стали no-op и не чистили БД.
 *
 * Для wallet дополнительно убирается запись из wallet_sync.
 * Для pool engine-state трейдеров живёт под entity_type='pool_trader'
 * (см. storage_delete_pool_traders_state).
 */
function storage_delete_entity(string $entityType, string $entityId): void
{
    $db = storage_db();
    storage_retry(function() use ($db, $entityType, $entityId) {
        $db->beginTransaction();
        try {
            // hl_orders/hl_locks тоже адресуются (entity_type, entity_id) — без их
            // чистки удалённая direct-сущность навсегда держала бы лизу символа
            // (блокируя его для других) и «висячие» записи ордеров. Реальные
            // ордера/позицию на бирже перед этим сворачивает hl_teardown_entity
            // (dashboard-обработчики); здесь — бэкстоп на уровне БД.
            foreach (['wallet_balances', 'open_positions', 'entity_state', 'seen_fills', 'watching', 'hl_orders', 'hl_locks'] as $table) {
                $db->prepare("DELETE FROM $table WHERE entity_type = ? AND entity_id = ?")
                   ->execute([$entityType, $entityId]);
            }

            if ($entityType === 'wallet') {
                $db->prepare('DELETE FROM wallet_sync WHERE wallet_id = ?')->execute([$entityId]);
            }

            $db->commit();
        } catch (Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    });
}

/**
 * Чистит engine-state (watermark+cooldown) и seen_fills всех трейдеров пула.
 * Их entity_type = 'pool_trader', entity_id = "{poolId}:{traderId}".
 * Вызывается при сбросе/удалении пула вместе с storage_delete_entity('pool', $poolId).
 */
function storage_delete_pool_traders_state(string $poolId): void
{
    $db   = storage_db();
    $like = $poolId . ':%';
    storage_retry(function() use ($db, $like) {
        $db->beginTransaction();
        try {
            $db->prepare("DELETE FROM entity_state WHERE entity_type = 'pool_trader' AND entity_id LIKE ?")
               ->execute([$like]);
            $db->prepare("DELETE FROM seen_fills WHERE entity_type = 'pool_trader' AND entity_id LIKE ?")
               ->execute([$like]);
            $db->commit();
        } catch (Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    });
}
