<?php
/**
 * POST /api/settings/update — Settings page save handler.
 * Plain form POST (not JSON/fetch), one section per submit (`section`
 * hidden field: profile | trading_defaults | notifications). Redirects
 * back to /settings?saved=<section> on success, or /settings?error=...
 * on a validation failure — matching onboarding/step2.php's own
 * redirect-with-error convention, not a JSON API response, since this
 * is a plain <form> submit, not fetch().
 */
require_once __DIR__ . '/../../src/PasswordPolicy.php';

$userId = $auth->requireAuth();

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    header('Location: /traderalign/settings');
    exit;
}

$section = $_POST['section'] ?? '';

function settingsRedirect($section, $error = null) {
    if ($error !== null) {
        header('Location: /traderalign/settings?error=' . urlencode($error) . '#' . $section);
    } else {
        header('Location: /traderalign/settings?saved=' . urlencode($section) . '#' . $section);
    }
    exit;
}

if ($section === 'profile') {
    $displayName = trim((string)($_POST['display_name'] ?? ''));
    if ($displayName === '' || mb_strlen($displayName) < 2) {
        settingsRedirect('profile', 'Preferred Username must be at least 2 characters.');
    }
    $displayName = mb_substr($displayName, 0, 40);

    $tradingStyle = $_POST['trading_style'] ?? '';
    $tradingStyle = in_array($tradingStyle, ['scalper', 'day_trader', 'swing', 'position'], true) ? $tradingStyle : null;

    $primaryBroker = trim((string)($_POST['primary_broker'] ?? ''));
    $primaryBroker = $primaryBroker === '' ? null : mb_substr($primaryBroker, 0, 255);

    // Currency selection is temporarily locked to USD, sitewide — most
    // trade/journal/report displays aren't currency-aware yet (see
    // CLAUDE.md's Currency/FX Domain section), so letting a trader pick a
    // different base_currency here produced a real, confusing mismatch:
    // Settings would show e.g. KES while trades/accounts kept showing
    // USD prices, since only Dashboard's cross-account aggregation was
    // ever wired to actually convert. settings.php's own <select> is
    // disabled for the same reason; this ignores whatever was posted
    // rather than trusting a bypassed/crafted request. Revisit once the
    // rest of the app is genuinely currency-aware.
    $baseCurrency = 'USD';

    $timezone = trim((string)($_POST['timezone'] ?? 'UTC'));
    // Accept any valid PHP timezone identifier, not just the page's own
    // dropdown list, since a pre-existing profile value may already carry
    // one outside that shortlist (the settings.php dropdown appends it as
    // an extra <option> in that case, so a round-trip submit is valid).
    if (!in_array($timezone, timezone_identifiers_list(), true)) {
        $timezone = 'UTC';
    }

    $db->update('user_profiles', $userId, [
        'display_name' => $displayName,
        'trading_style' => $tradingStyle,
        'primary_broker' => $primaryBroker,
        'base_currency' => $baseCurrency,
        'timezone' => $timezone,
    ], 'user_id');

    settingsRedirect('profile');
}

if ($section === 'trading_defaults') {
    $riskMode = $_POST['default_risk_mode'] ?? '';
    $riskMode = in_array($riskMode, ['auto', 'fixed', 'manual', 'rr'], true) ? $riskMode : null;

    $riskPctRaw = trim((string)($_POST['default_risk_pct'] ?? ''));
    $riskPct = null;
    if ($riskPctRaw !== '') {
        if (!is_numeric($riskPctRaw) || (float)$riskPctRaw <= 0 || (float)$riskPctRaw > 100) {
            settingsRedirect('trading-defaults', 'Default Risk % must be a number between 0 and 100.');
        }
        $riskPct = round((float)$riskPctRaw, 2);
    }

    $db->update('user_profiles', $userId, [
        'default_risk_mode' => $riskMode,
        'default_risk_pct' => $riskPct,
    ], 'user_id');

    settingsRedirect('trading-defaults');
}

if ($section === 'notifications') {
    $db->update('user_profiles', $userId, [
        'email_notifications' => isset($_POST['email_notifications']) ? 1 : 0,
    ], 'user_id');

    settingsRedirect('notifications');
}

if ($section === 'security') {
    // CSRF protection — this is the one settings section that mutates a
    // credential, so it's the one section that requires the token
    // (profile/trading_defaults/notifications don't send one; enforcing
    // it here only, not globally, avoids breaking those existing forms).
    if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
        !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
        settingsRedirect('security', 'Your session expired. Please try again.');
    }
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));

    $currentPassword = (string)($_POST['current_password'] ?? '');
    $newPassword = (string)($_POST['new_password'] ?? '');
    $confirmPassword = (string)($_POST['confirm_new_password'] ?? '');

    if ($currentPassword === '' || $newPassword === '' || $confirmPassword === '') {
        settingsRedirect('security', 'All password fields are required.');
    }

    // users is intentionally outside ScopedDb's table allowlist (per
    // src/ScopedDb.php) — password reads/writes always go through a
    // direct, explicitly user_id-scoped PDO query, same as every other
    // auth entry point in this app.
    $stmt = $pdo->prepare('SELECT email, password_hash FROM users WHERE id = :uid AND deleted_at IS NULL');
    $stmt->execute([':uid' => $userId]);
    $userRow = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$userRow || !password_verify($currentPassword, $userRow['password_hash'])) {
        settingsRedirect('security', 'Current password is incorrect.');
    }

    if ($newPassword !== $confirmPassword) {
        settingsRedirect('security', 'New passwords do not match.');
    }

    $policyErrors = PasswordPolicy::validate($newPassword, ['email' => $userRow['email']]);
    if (!empty($policyErrors)) {
        settingsRedirect('security', implode(' ', $policyErrors));
    }

    if (password_verify($newPassword, $userRow['password_hash'])) {
        settingsRedirect('security', 'New password must be different from your current password.');
    }

    $newHash = password_hash($newPassword, PASSWORD_ARGON2ID, PasswordPolicy::hashOptions());
    $upd = $pdo->prepare(
        'UPDATE users
         SET password_hash = :hash, failed_login_attempts = 0, last_failed_login = NULL,
             last_password_change = NOW(), updated_at = NOW()
         WHERE id = :uid'
    );
    $upd->execute([':hash' => $newHash, ':uid' => $userId]);

    // Rotate the session id after a credential change — standard defense
    // against session fixation around a security-sensitive action.
    session_regenerate_id(true);

    settingsRedirect('security');
}

// Unrecognized section
header('Location: /traderalign/settings');
exit;
