<?php
// =============================================================
// DASHBOARD/LIB/SETTINGS_HANDLER — обработка POST страницы настроек
// =============================================================
// CSRF проверяется вызывающей страницей. Возвращает ['message','messageType'].

require_once __DIR__ . '/../../config.php';
require_once __DIR__ . '/../../storage.php';
require_once __DIR__ . '/../../hl_executor.php';   // hl_teardown_entity: свернуть direct-след при сбросе
require_once __DIR__ . '/forms.php';

/**
 * Чистый билдер глобальных настроек из POST — без I/O и без валидации URL
 * (её делает вызывающий обработчик). Тестируется напрямую.
 */
function dashboard_build_global_settings(array $post): array
{
    $symbolsRaw = trim($post['allowed_symbols'] ?? '');
    $allowedSymbols = [];
    if ($symbolsRaw !== '') {
        $parts = preg_split('/[\s,]+/', strtoupper($symbolsRaw));
        $allowedSymbols = array_values(array_filter(array_map('trim', $parts)));
    }

    $symbolLeverageRaw = trim($post['symbol_leverage'] ?? '');
    $symbolLeverage = [];
    if ($symbolLeverageRaw !== '') {
        foreach (preg_split('/[\s,]+/', $symbolLeverageRaw) as $pair) {
            if (strpos($pair, '=') === false) continue;
            [$sym, $lev] = explode('=', $pair, 2);
            $sym = trim(strtoupper($sym));
            $lev = (int) trim($lev);
            if ($sym !== '' && $lev > 0) {
                $symbolLeverage[$sym] = $lev;
            }
        }
    }

    $built = [
        'allowed_symbols'   => $allowedSymbols,
        'scale_factor'      => (float) ($post['scale_factor'] ?? 0.1),
        'default_leverage'  => max(1, (int) ($post['default_leverage'] ?? 3)),
        'symbol_leverage'   => $symbolLeverage,
        'cooldown_seconds'  => max(0, (int) ($post['cooldown_seconds'] ?? 30)),
        'poll_interval_minutes' => max(0, (float) ($post['poll_interval_minutes'] ?? 0)),
        'executor_mode'     => ($post['executor_mode'] ?? 'mock') === 'live' ? 'live' : 'mock',
        'executor_url'      => trim($post['executor_url'] ?? ''),

        // --- Симулятор копитрейдинга ---
        'virtual_balance'   => max(0, (float) ($post['virtual_balance'] ?? 1000)),
        'sizing_mode'       => in_array($post['sizing_mode'] ?? 'fixed', ['fixed', 'percent_balance', 'proportional'], true)
            ? ($post['sizing_mode'] ?? 'fixed') : 'fixed',
        'sizing_fixed_usd'  => max(0, (float) ($post['sizing_fixed_usd'] ?? 100)),
        'sizing_percent'    => max(0, min(100, (float) ($post['sizing_percent'] ?? 10))),
        // Лимиты объёма НАШЕЙ позиции (нотионал); 0 = выкл
        'position_min_usd'  => max(0, (float) ($post['position_min_usd'] ?? 0)),
        'position_max_usd'  => max(0, (float) ($post['position_max_usd'] ?? 0)),
        'fee_pct_per_side'             => max(0, (float) ($post['fee_pct_per_side'] ?? 0.035)),
        'slippage_pct_per_side'        => max(0, (float) ($post['slippage_pct_per_side'] ?? 0)),
        'funding_rate_hourly_pct'      => (float) ($post['funding_rate_hourly_pct'] ?? 0),
        'partial_close_mode'           => (($post['partial_close_mode'] ?? 'off') === 'on') ? 'on' : 'off',
        'copy_after_drawdown_enabled'  => !empty($post['copy_after_drawdown_enabled']) && $post['copy_after_drawdown_enabled'] === '1',
        'copy_after_drawdown_pct'      => max(0, min(100, (float) ($post['copy_after_drawdown_pct'] ?? 5))),
        'copy_after_drawdown_ttl_hours' => max(0, (float) ($post['copy_after_drawdown_ttl_hours'] ?? 0)),
        // --- Уведомления в Telegram ---
        'telegram_notify'    => !empty($post['telegram_notify']) && $post['telegram_notify'] === '1',
        'telegram_bot_token' => trim($post['telegram_bot_token'] ?? ''),
        'telegram_chat_id'   => trim($post['telegram_chat_id'] ?? ''),
    ];

    // --- Прямая торговля (вкладка «Исполнение») ---
    // Эти ключи пишутся ТОЛЬКО когда поле реально пришло в POST. Раньше их в
    // форме не было вовсе и они правились руками в wallets.json; теперь форма
    // их показывает, но частичный POST (из другой формы или скрипта) по-прежнему
    // не должен молча откатывать direct-режим на webhook.
    if (array_key_exists('execution_mode', $post)) {
        $built['execution_mode'] = $post['execution_mode'] === 'direct' ? 'direct' : 'webhook';
    }
    if (array_key_exists('hl_margin_mode', $post)) {
        $built['hl_margin_mode'] = $post['hl_margin_mode'] === 'cross' ? 'cross' : 'isolated';
    }
    if (array_key_exists('hl_subaccount', $post)) {
        $sub = trim((string) $post['hl_subaccount']);
        $built['hl_subaccount'] = preg_match('/^0x[0-9a-fA-F]{40}$/', $sub) ? strtolower($sub) : '';
    }

    return $built;
}

/**
 * Сбрасывает state ВСЕХ кошельков (engine/positions/watching/balance) и,
 * опционально, логи + историю в БД. Возвращает список ошибок (пустой = успех).
 */
function dashboard_reset_all_wallets_state(array $wallets, array $globalSettings, bool $clearLogs, bool $clearSqlite): array
{
    $errors = [];

    foreach ($wallets as $w) {
        // Тот же per-wallet lock, что у бота: сброс посреди prepare→commit
        // молча перезаписывался бы старым watermark (см. reset_wallet).
        $lockFp = dashboard_acquire_wallet_lock($w['id'], 1.0);
        if ($lockFp === null) {
            $errors[] = $w['id'] . ': бот обрабатывает кошелёк, сброс пропущен — повторите';
            continue;
        }
        try {
            $walletSettings = resolve_wallet_settings($w, $globalSettings);
            $startBalance   = (float) $walletSettings['virtual_balance'];

            // Свернуть direct-след (реальные ордера/позиция/лизы) до сброса бумаги.
            hl_teardown_entity('wallet', $w['id'], $w['label'] ?? $w['id']);

            storage_save_state('wallet', $w['id'], [
                'trades'       => [],
                'cooldown'     => [],
                'last_seen_ts' => null,
            ]);
            storage_save_positions('wallet', $w['id'], []);
            storage_save_watching('wallet', $w['id'], []);
            storage_save_balance('wallet', $w['id'], dashboard_fresh_wallet_balance($startBalance));
        } catch (Throwable $e) {
            $errors[] = $w['id'] . ': ' . $e->getMessage();
        } finally {
            dashboard_release_wallet_lock($lockFp);
        }
    }

    if ($clearLogs) {
        if (file_exists(LOG_FILE)) {
            if (file_put_contents(LOG_FILE, '', LOCK_EX) === false) {
                $errors[] = 'bot.log';
            }
        }
        if ($clearSqlite) {
            try {
                $db = storage_db();
                $db->exec('DELETE FROM signals');
                $db->exec('DELETE FROM trades_closed');
            } catch (Throwable $e) {
                $errors[] = 'sqlite: ' . $e->getMessage();
            }
        }
    }

    return $errors;
}

/**
 * Обработчик POST страницы настроек: save_global | reset_all.
 */
function dashboard_handle_settings_post(array $post, array $config): array
{
    $action         = $post['action'] ?? '';
    $globalSettings = $config['global_settings'];
    $wallets        = $config['wallets'];
    $availableTags  = $config['available_tags'] ?? [];

    if ($action === 'save_global') {
        // Валидация executor_url: только http/https, запрет приватных адресов
        $executorUrlRaw = trim($post['executor_url'] ?? '');
        if ($executorUrlRaw !== '' && !dashboard_executor_url_is_valid($executorUrlRaw)) {
            return ['message' => 'executor_url: допустимы только внешние http/https адреса', 'messageType' => 'error'];
        }

        // Мержим поверх ТЕКУЩИХ глобальных настроек, а не заменяем целиком:
        // билдер знает только поля формы, и без мержа всё, чего в форме нет
        // (execution_mode, hl_*, stop_loss/take_profit/grid, webhooks — заданные
        // руками в wallets.json), молча откатывалось бы к заводским дефолтам
        // при каждом «Сохранить глобальные настройки».
        $globalSettings = array_merge($globalSettings, dashboard_build_global_settings($post));

        if (save_wallets_config(['global_settings' => $globalSettings, 'wallets' => $wallets, 'available_tags' => $availableTags])) {
            return ['message' => 'Глобальные настройки сохранены', 'messageType' => 'success'];
        }
        return ['message' => 'Ошибка сохранения настроек', 'messageType' => 'error'];
    }

    // Отдельный экшен для срока хранения сигналов: поле живёт на вкладке
    // «Система» ВНЕ общей формы save_global (туда её не растянуть — внутри
    // вкладки уже есть форма reset_all, а формы не вкладываются). Мержим
    // только один ключ — остальные глобальные настройки не затрагиваются.
    if ($action === 'save_retention') {
        $days = max(0, (int) ($post['signals_retention_days'] ?? 0));
        $globalSettings['signals_retention_days'] = $days;

        if (save_wallets_config(['global_settings' => $globalSettings, 'wallets' => $wallets, 'available_tags' => $availableTags])) {
            return [
                'message' => $days > 0
                    ? "Срок хранения сигналов: $days дн. Старые сигналы будут удаляться автоматически (раз в сутки)."
                    : 'Автоочистка сигналов выключена — история хранится бессрочно.',
                'messageType' => 'success',
            ];
        }
        return ['message' => 'Ошибка сохранения настроек', 'messageType' => 'error'];
    }

    if ($action === 'reset_all') {
        $clearLogs   = !empty($post['clear_logs']);
        $clearSqlite = $clearLogs && !empty($post['clear_sqlite']);
        $errors = dashboard_reset_all_wallets_state($wallets, $globalSettings, $clearLogs, $clearSqlite);

        if (empty($errors)) {
            return [
                'message'     => 'State всех кошельков сброшен' . ($clearLogs ? ' (логи и CSV очищены)' : ' (логи сохранены)') . '.',
                'messageType' => 'success',
            ];
        }
        return ['message' => 'Ошибка при сбросе: ' . implode(', ', $errors), 'messageType' => 'error'];
    }

    // --- Восстановление конфига из бэкапа ---
    if ($action === 'restore_backup') {
        $name = basename((string) ($post['backup_name'] ?? ''));
        if ($name === '' || !str_ends_with($name, '.json')) {
            return ['message' => 'Не указан бэкап', 'messageType' => 'error'];
        }
        $target = str_starts_with($name, 'pools-') ? 'pools.json' : 'wallets.json';

        if (config_restore_backup($target, $name)) {
            $restored = load_wallets_config();
            return [
                'message' => 'Восстановлено из ' . $name . '. Трейдеров в списке: '
                           . count($restored['wallets']) . '. Прежняя версия сохранена в бэкапы.',
                'messageType' => 'success',
            ];
        }
        return ['message' => 'Не удалось восстановить из ' . $name, 'messageType' => 'error'];
    }

    return ['message' => '', 'messageType' => ''];
}
