<?php
namespace App\Nexus\Http\Controllers;

use App\Models\Bid;
use App\Models\Plan;
use App\Models\Sell;
use App\Nexus\Analytics\BotAnalyticsService;
use App\Nexus\Analytics\DashboardService;
use App\Nexus\Analytics\MarketMetricsService;
use App\Nexus\Analytics\UserMetricsService;
use App\Nexus\Engine\EconomyGuardian;
use App\Nexus\Engine\NexusAuditLogger;
use App\Nexus\Engine\NexusConfig;
use App\Nexus\Models\NexusConfig as NexusConfigModel;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
 * NEXUS API Controller
 *
 * Handles all JSON API calls from the dashboard JavaScript.
 * GET endpoints for live data. POST endpoints for admin actions.
 * All POST endpoints validate input and log changes to audit trail.
 *
 * PHASE 6.5 ADDITIONS:
 * - sessionDetail: expanded session info for AJAX drill-down
 * - usersTable: AJAX table with sort/search/pagination
 * - usersExport: CSV download of filtered users
 * - sessionExport: CSV download of session history
 * - marketWinRatesExport: CSV download
 * - categoryActivityExport: CSV download
 * - availableMonths: month list for selector dropdown
 *
 * All routes protected by nexus.admin middleware (applied in route group).
 *
 * @package App\Nexus\Http\Controllers
 */
class NexusApiController
{
    // ── EXISTING GET ENDPOINTS ──

    /**
     * Current session status for live countdown and session indicator.
     */
    public function sessionStatus(): JsonResponse
    {
        $dashboard = new DashboardService();
        return response()->json([
            'success' => true,
            'data'    => $dashboard->getSessionMetrics(),
        ]);
    }

    /**
     * Economy health indicators.
     */
    public function economyHealth(): JsonResponse
    {
        $guardian = new EconomyGuardian();
        return response()->json([
            'success' => true,
            'data'    => $guardian->getEconomyHealth(),
        ]);
    }

    /**
     * Top bidders for the leaderboard widget.
     */
    public function topBidders(): JsonResponse
    {
        $userService = new UserMetricsService();
        return response()->json([
            'success' => true,
            'data'    => $userService->getTopBidders(10),
        ]);
    }

    /**
     * Pending sells count and volume.
     */
    public function pendingSells(): JsonResponse
    {
        $manualCount = Sell::where('type', 'manual_sell')->where('status', 0)->count();
        $aiCount     = Sell::where('type', '!=', 'manual_sell')->where('status', 0)->count();
        $volume      = Sell::where('status', 0)->sum(DB::raw('amount * quantity'));
        return response()->json([
            'success' => true,
            'data'    => [
                'count'  => $manualCount + $aiCount,
                'volume' => round((float) $volume, 2),
                'manual' => $manualCount,
                'ai'     => $aiCount,
            ],
        ]);
    }

    /**
     * Paginated audit log with filter support.
     */
    public function auditLog(Request $request): JsonResponse
    {
        $query = DB::table('nexus_audit_log')->orderBy('created_at', 'desc');

        if ($request->filled('event')) {
            $query->where('event', $request->input('event'));
        }
        if ($request->filled('user_id')) {
            $query->where('subject_user_id', $request->input('user_id'));
        }
        if ($request->filled('session_id')) {
            $query->where('nexus_session_id', $request->input('session_id'));
        }
        if ($request->filled('limit')) {
            $limit = min(100, max(1, (int) $request->input('limit')));
        } else {
            $limit = 20;
        }

        $logs = $query->paginate($limit);

        return response()->json([
            'success' => true,
            'data'    => $logs,
        ]);
    }

    // ── EXISTING POST ENDPOINTS ──

    /**
     * Update a single nexus_config parameter.
     */
    public function updateConfig(Request $request): JsonResponse
    {
        $key   = $request->input('key');
        $value = $request->input('value');

        if (empty($key) || $value === null) {
            return response()->json(['success' => false, 'message' => 'Key and value are required'], 422);
        }

        if ($key === 'nexus_status') {
            return response()->json(['success' => false, 'message' => 'nexus_status cannot be changed via API. Manual change only at go-live.'], 403);
        }

        $configRow = NexusConfigModel::where('key', $key)->first();
        if (! $configRow) {
            return response()->json(['success' => false, 'message' => "Config key '{$key}' does not exist"], 422);
        }

        if (! $configRow->is_editable) {
            return response()->json(['success' => false, 'message' => "Config key '{$key}' is not editable"], 403);
        }

        if (in_array($configRow->type, ['integer', 'decimal']) && ! is_numeric($value)) {
            return response()->json(['success' => false, 'message' => "Value must be numeric for '{$key}'"], 422);
        }

        $admin    = $request->nexusAdmin ?? auth('nexus')->user();
        $adminId  = $admin ? $admin->id : 0;
        $oldValue = $configRow->value;

        NexusConfig::set($key, (string) $value, $adminId);

        return response()->json([
            'success' => true,
            'message' => "Config '{$key}' updated",
            'data' => ['key' => $key, 'old_value' => $oldValue, 'new_value' => $value],
        ]);
    }

    /**
     * Toggle emergency pause on/off.
     */
    public function toggleEmergencyPause(Request $request): JsonResponse
    {
        $admin   = $request->nexusAdmin ?? auth('nexus')->user();
        $adminId = $admin ? $admin->id : 0;

        $currentValue = NexusConfig::get('emergency_pause', '0');
        $newValue     = $currentValue === '1' ? '0' : '1';

        NexusConfig::set('emergency_pause', $newValue, $adminId);

        $status = $newValue === '1' ? 'PAUSED' : 'RESUMED';

        return response()->json([
            'success' => true,
            'message' => "Trading {$status}",
            'data' => ['emergency_pause' => $newValue, 'is_paused' => $newValue === '1'],
        ]);
    }

    /**
     * Update NEXUS plan ranges for a given plan.
     */
    public function updatePlanRanges(Request $request): JsonResponse
    {
        $planId = $request->input('plan_id');
        if (! $planId) {
            return response()->json(['success' => false, 'message' => 'plan_id is required'], 422);
        }

        $plan = Plan::find($planId);
        if (! $plan) {
            return response()->json(['success' => false, 'message' => 'Plan not found'], 404);
        }

        $fields = ['nexus_daily_min', 'nexus_daily_max', 'nexus_monthly_min', 'nexus_monthly_max', 'nexus_success_rate_min', 'nexus_success_rate_max'];

        $updates   = [];
        $oldValues = [];
        foreach ($fields as $field) {
            if ($request->has($field)) {
                $val = $request->input($field);
                if (! is_numeric($val)) {
                    return response()->json(['success' => false, 'message' => "{$field} must be numeric"], 422);
                }
                $oldValues[$field] = $plan->$field;
                $updates[$field]   = (float) $val;
            }
        }

        if (empty($updates)) {
            return response()->json(['success' => false, 'message' => 'No valid fields to update'], 422);
        }

        $plan->update($updates);

        $admin = $request->nexusAdmin ?? auth('nexus')->user();
        NexusAuditLogger::logAdminAction(
            $admin ? $admin->id : 0,
            'plan_ranges_updated',
            'plan',
            $planId,
            "Plan '{$plan->name}' NEXUS ranges updated",
            ['old' => $oldValues, 'new' => $updates]
        );

        return response()->json([
            'success' => true,
            'message' => "Plan '{$plan->name}' ranges updated",
            'data' => $updates,
        ]);
    }

    // ── PHASE 6.5 NEW ENDPOINTS ──

    /**
     * Expanded session detail for AJAX drill-down.
     */
    public function sessionDetail(int $id): JsonResponse
    {
        $dashboard = new DashboardService();
        $detail    = $dashboard->getSessionDetail($id);

        if (isset($detail['error'])) {
            return response()->json(['success' => false, 'message' => $detail['error']], 404);
        }

        return response()->json(['success' => true, 'data' => $detail]);
    }

    /**
     * AJAX users table with sort, search, pagination, date range.
     */
    public function usersTable(Request $request): JsonResponse
    {
        $userService = new UserMetricsService();

        $limit  = min(100, max(1, (int) $request->input('limit', 20)));
        $page   = max(1, (int) $request->input('page', 1));
        $offset = ($page - 1) * $limit;

        // Parse date range
        $start = $request->input('start', Carbon::now()->startOfMonth()->toDateString());
        $end   = $request->input('end', Carbon::now()->toDateString());

        if ($request->filled('month')) {
            $month = Carbon::parse($request->input('month') . '-01');
            $start = $month->startOfMonth()->toDateString();
            $end   = $month->endOfMonth()->toDateString();
        }

        $result = $userService->getTopBidders(
            $limit,
            $start,
            $end,
            $request->input('sort', 'wins'),
            $request->input('dir', 'desc'),
            $request->input('search'),
            $offset
        );

        return response()->json(['success' => true, 'data' => $result]);
    }

    /**
     * CSV export of filtered/sorted users table.
     * Max 1000 rows.
     */
    public function usersExport(Request $request): StreamedResponse
    {
        $userService = new UserMetricsService();

        $start = $request->input('start', Carbon::now()->startOfMonth()->toDateString());
        $end   = $request->input('end', Carbon::now()->toDateString());

        if ($request->filled('month')) {
            $month = Carbon::parse($request->input('month') . '-01');
            $start = $month->startOfMonth()->toDateString();
            $end   = $month->endOfMonth()->toDateString();
        }

        $result = $userService->getTopBidders(
            1000,
            $start,
            $end,
            $request->input('sort', 'wins'),
            $request->input('dir', 'desc'),
            $request->input('search'),
            0
        );

        $data     = $result['data'] ?? [];
        $filename = 'nexus_users_' . $start . '_to_' . $end . '.csv';

        return $this->streamCsv($filename, ['#', 'User', 'Wins', 'Losses', 'Win Rate', 'Profit', 'Volume', 'Profit %', 'Bids', 'ICTP Bids'], $data, function ($row, $index) {
            return [
                $index + 1,
                $row['username'],
                $row['wins'],
                $row['losses'],
                $row['win_rate'] . '%',
                '$' . number_format($row['total_profit'], 2),
                '$' . number_format($row['total_volume'], 2),
                $row['profit_percent'] . '%',
                $row['total_bids'] ?? 0,
                $row['ictp_bids'] ?? 0,
            ];
        });
    }

    /**
     * CSV export of session history.
     */
    public function sessionExport(Request $request): StreamedResponse
    {
        $dashboard = new DashboardService();

        $start = $request->input('start', Carbon::now()->subDays(30)->toDateString());
        $end   = $request->input('end', Carbon::now()->toDateString());

        if ($request->filled('month')) {
            $month = Carbon::parse($request->input('month') . '-01');
            $start = $month->startOfMonth()->toDateString();
            $end   = $month->endOfMonth()->toDateString();
        }

        $sessions = $dashboard->getSessionHistory($start, $end, 500);
        $filename = 'nexus_sessions_' . $start . '_to_' . $end . '.csv';

        return $this->streamCsv($filename, ['Date', 'Session', 'Status', 'Bids', 'Evaluated', 'Approved', 'Rejected', 'Win Rate Target', 'Win Rate Actual', 'Unique Bidders'], $sessions, function ($row, $index) {
            return [
                $row['session_date'],
                'S' . $row['session_number'],
                $row['status'],
                $row['total_bids'],
                $row['evaluated'],
                $row['approved'],
                $row['rejected'],
                $row['win_rate_target'] . '%',
                $row['win_rate_actual'] . '%',
                $row['unique_bidders'],
            ];
        });
    }

    /**
     * CSV export of market win rates.
     */
    public function marketWinRatesExport(Request $request): StreamedResponse
    {
        $marketService = new MarketMetricsService();

        $start = $request->input('start', Carbon::now()->startOfMonth()->toDateString());
        $end   = $request->input('end', Carbon::now()->toDateString());

        if ($request->filled('month')) {
            $month = Carbon::parse($request->input('month') . '-01');
            $start = $month->startOfMonth()->toDateString();
            $end   = $month->endOfMonth()->toDateString();
        }

        $data     = $marketService->getMarketWinRates($start, $end);
        $filename = 'nexus_market_win_rates_' . $start . '_to_' . $end . '.csv';

        return $this->streamCsv($filename, ['#', 'Market', 'Evaluated', 'Won', 'Lost', 'Win Rate'], $data, function ($row, $index) {
            return [
                $index + 1,
                $row['market_name'],
                $row['total_sells'],
                $row['wins'],
                $row['losses'],
                $row['win_rate'] . '%',
            ];
        });
    }

    /**
     * CSV export of category activity.
     */
    public function categoryActivityExport(Request $request): StreamedResponse
    {
        $marketService = new MarketMetricsService();

        $start = $request->input('start', Carbon::now()->startOfMonth()->toDateString());
        $end   = $request->input('end', Carbon::now()->toDateString());

        if ($request->filled('month')) {
            $month = Carbon::parse($request->input('month') . '-01');
            $start = $month->startOfMonth()->toDateString();
            $end   = $month->endOfMonth()->toDateString();
        }

        $data     = $marketService->getCategoryActivity($start, $end);
        $filename = 'nexus_categories_' . $start . '_to_' . $end . '.csv';

        return $this->streamCsv($filename, ['Category', 'Bids', 'Sells', 'Total'], $data, function ($row, $index) {
            return [
                $row['category'],
                $row['bids'],
                $row['sells'],
                $row['bids'] + $row['sells'],
            ];
        });
    }

    /**
     * Available months for the date selector dropdown.
     */
    public function availableMonths(): JsonResponse
    {
        $userService = new UserMetricsService();
        return response()->json([
            'success' => true,
            'data'    => $userService->getAvailableMonths(),
        ]);
    }

    // ── PHASE 8 -- BOT GOVERNANCE ──

    /**
     * AJAX paginated bot table for a single plan.
     * Supports sort, search, date range.
     */
    public function botsTable(Request $request): JsonResponse
    {
        $botService = new \App\Nexus\Analytics\BotAnalyticsService();

        $planId = (int) $request->input('plan_id', 0);
        if (! $planId) {
            return response()->json(['success' => false, 'message' => 'plan_id required'], 422);
        }

        $limit  = min(100, max(1, (int) $request->input('limit', 20)));
        $page   = max(1, (int) $request->input('page', 1));
        $offset = ($page - 1) * $limit;

        $start = $request->input('start', Carbon::now()->startOfMonth()->toDateString());
        $end   = $request->input('end', Carbon::now()->toDateString());

        if ($request->filled('month')) {
            $month = Carbon::parse($request->input('month') . '-01');
            $start = $month->startOfMonth()->toDateString();
            $end   = $month->endOfMonth()->toDateString();
        }

        $result = $botService->getBotsTable(
            $planId,
            $start,
            $end,
            $request->input('sort', 'capital'),
            $request->input('dir', 'desc'),
            $request->input('search'),
            $limit,
            $offset
        );

        return response()->json(['success' => true, 'data' => $result]);
    }

    /**
     * CSV export of bots. Optional ?plan_id= filter, date-aware.
     */
    public function botsExport(Request $request): StreamedResponse
    {
        $botService = new \App\Nexus\Analytics\BotAnalyticsService();

        $planId = $request->input('plan_id') ? (int) $request->input('plan_id') : null;
        $bots   = $botService->getBotsForExport($planId);

        $planName = $planId ? ('plan_' . $planId) : 'all';
        $filename = 'nexus_bots_' . $planName . '_' . Carbon::now()->format('Y-m-d') . '.csv';

        return $this->streamCsv($filename, [
            'Bot ID',
            'User',
            'Plan',
            'Capital',
            'Balance',
            'Profit',
            'Profit %',
            'Target %',
            'vs Target %',
            'Status',
            'Drift',
            'Days Elapsed',
            'Days Left',
            'Trade Min',
            'Trade Max',
            'Base Success %',
            'Buy Date',
            'End Date',
        ], $bots, function ($row, $index) {
            return [
                $row['bot_id'],
                $row['username'],
                $row['plan_name'],
                '$' . number_format($row['capital'], 2),
                '$' . number_format($row['balance'], 2),
                '$' . number_format($row['current_profit'], 2),
                $row['profit_pct'] . '%',
                $row['target_pct'] . '%',
                $row['profit_of_target'] . '%',
                $row['track_status'],
                '$' . number_format($row['drift'], 2),
                $row['days_elapsed'],
                $row['days_remaining'],
                $row['trade_min'],
                $row['trade_max'],
                $row['base_success'] . '%',
                $row['buy_date'],
                $row['end_date'],
            ];
        });
    }

    // ── TERMINAL: ADMIN LIVE TERMINAL ──

    /**
     * Aggregated polling endpoint for the admin live terminal.
     * Single request returns all terminal data.
     * Most data served from Laravel Cache (30-60s TTL).
     * Fresh queries limited to audit log (1 indexed query) and cache misses.
     *
     * GET /nexus/api/terminal/stream?since={ISO_timestamp}
     */
    public function terminalStream(Request $request): JsonResponse
    {
        $since         = $request->input('since');
        $dashboard     = new DashboardService();
        $botService    = new BotAnalyticsService();
        $guardian      = new EconomyGuardian();
        $marketService = new MarketMetricsService();

        // Session metrics (cached 30s)
        $sessionMetrics = $dashboard->getSessionMetrics();

        // NEXUS-specific sell stats + yesterday deltas (cached 30s)
        $terminalStats = $dashboard->getTerminalStats();

        // Economy health (cached 30s after Step A1 fix)
        $economy = $guardian->getEconomyHealth();

        // Fairness score (cached 60s)
        $economyIndicators = $marketService->getEconomyIndicators();

        // Bot glance (cached 60s)
        $botGlance = $botService->getGlanceSummary();

        // Bot alerts (cached 60s)
        $botAlerts = $botService->getAlerts();

        // Session times from config (cached 300s)
        $schedule         = [];
        $sessionsToday    = $sessionMetrics['sessions_today'] ?? [];
        $sessionStatusMap = [];
        foreach ($sessionsToday as $s) {
            $sessionStatusMap[$s['session_number'] ?? $s->session_number ?? 0] = $s['status'] ?? $s->status ?? 'pending';
        }

        for ($i = 1; $i <= 3; $i++) {
            $time     = NexusConfig::get('session_' . $i . '_start_utc', '00:00');
            $labels   = [1 => 'ASIA-PAC', 2 => 'EUROPE', 3 => 'AMERICAS'];
            $duration = NexusConfig::getInt('session_duration_minutes', 60);
            $endTime  = Carbon::parse($time)->addMinutes($duration)->format('H:i');

            $status = $sessionStatusMap[$i] ?? 'pending';
            if ($status === 'completed' || $status === 'processing') {
                $status = 'completed';
            }

            $schedule[] = [
                'number'   => $i,
                'time'     => $time,
                'end_time' => $endTime,
                'label'    => $labels[$i] ?? 'SESSION ' . $i,
                'status'   => $status,
            ];
        }

        // Audit log events (NOT cached -- indexed query, lightweight)
        $auditQuery = DB::table('nexus_audit_log')
            ->orderByDesc('created_at')
            ->limit(50);
        if ($since) {
            $auditQuery->where('created_at', '>', $since);
        }
        $events = $auditQuery->get()->map(function ($row) {
            $context = is_string($row->context) ? json_decode($row->context, true) : (array) ($row->context ?? []);
            return [
                'id'         => $row->id,
                'event'      => $row->event,
                'actor_type' => $row->actor_type ?? 'system',
                'actor_id'   => $row->actor_id,
                'user_id'    => $row->subject_user_id,
                'session_id' => $row->nexus_session_id,
                'sell_id'    => $row->sell_id,
                'bid_id'     => $row->bid_id,
                'context'    => $context,
                'notes'      => $row->notes,
                'created_at' => $row->created_at,
            ];
        })->toArray();

        // Bot trade feed: recent AI bid wins + sell completions from actual tables
        // This runs even when NEXUS is inactive -- bots trade through Kernel.php pipeline
        $botFeed = $this->buildAdminBotFeed($since);
        if (! empty($botFeed)) {
            $events = array_merge($events, $botFeed);
            usort($events, function ($a, $b) {
                return strcmp($b['created_at'] ?? '', $a['created_at'] ?? '');
            });
            $events = array_slice($events, 0, 80);
        }

        // Chart data: 7-day daily bids + volume (cached 60s via getTradingActivityChart)
        $weekAgo   = Carbon::now()->subDays(7)->toDateString();
        $today     = Carbon::today()->toDateString();
        $chartData = $dashboard->getTradingActivityChart($weekAgo, $today);

        // 7-day daily volume for mini chart (fill empty days with 0)
        $dailyVolume = Cache::remember('nexus.terminal.daily_volume', 60, function () {
            $since = Carbon::now()->subDays(7)->startOfDay();
            $raw   = Bid::where('type', 'manual_bid')
                ->where('bid_time', '>=', $since)
                ->selectRaw('DATE(bid_time) as d, COALESCE(SUM(amount * quantity), 0) as vol')
                ->groupByRaw('DATE(bid_time)')
                ->pluck('vol', 'd')
                ->toArray();

            // Fill all 8 days (7 back + today) so array matches chart labels
            $filled = [];
            $cursor = $since->copy();
            $end    = Carbon::today();
            while ($cursor->lessThanOrEqualTo($end)) {
                $key      = $cursor->toDateString();
                $filled[] = round((float) ($raw[$key] ?? 0), 2);
                $cursor->addDay();
            }
            return $filled;
        });

        // Hero chart seed (cached 60s)
        $chartSeed = $dashboard->getTerminalChartSeed();

        // Pending sells count
        $pendingSells = Sell::where('type', 'manual_sell')->where('status', 0)->count();

        return response()->json([
            'success' => true,
            'data'    => [
                'engine'     => [
                    'status'       => NexusConfig::isNexusActive() ? 'active' : 'inactive',
                    'paused'       => NexusConfig::isEmergencyPaused(),
                    'version'      => '1.0',
                    'uptime_label' => NexusConfig::isNexusActive()
                        ? (NexusConfig::isEmergencyPaused() ? 'Emergency pause active' : 'Engine operational')
                        : 'Engine inactive',
                ],
                'session'    => [
                    'is_open'            => $sessionMetrics['is_open'],
                    'active'             => $sessionMetrics['active_session'],
                    'seconds_remaining'  => $sessionMetrics['active_session']['seconds_remaining'] ?? 0,
                    'next_session_time'  => $sessionMetrics['next_session_time'] ?? null,
                    'seconds_until_next' => $sessionMetrics['seconds_until_next'] ?? 0,
                    'sessions_today'     => $sessionsToday,
                ],
                'stats'      => [
                    'total_bids'            => $sessionMetrics['total_bids'],
                    'total_volume'          => $sessionMetrics['total_volume'],
                    'unique_bidders'        => $sessionMetrics['unique_bidders'] ?? 0,
                    'pending_sells'         => $pendingSells,
                    'win_rate'              => $sessionMetrics['win_rate'] ?? 0,
                    'total_sells_evaluated' => $terminalStats['total_sells_evaluated'],
                    'total_sells_approved'  => $terminalStats['total_sells_approved'],
                    'total_sells_rejected'  => $terminalStats['total_sells_rejected'],
                    'total_profit'          => $terminalStats['total_profit'],
                    'current_hour_volume'   => $terminalStats['current_hour_volume'] ?? 0,
                    'cumulative_total'      => $terminalStats['cumulative_total'] ?? 0,
                    'deltas'                => $terminalStats['deltas'],
                ],
                'economy'    => [
                    'liquidity_index'    => $economy['liquidity_index'] ?? 0,
                    'fairness'           => $economyIndicators['fairness']['value'] ?? 0,
                    'win_rate_avg'       => $economy['win_rate_avg'] ?? 0,
                    'users_at_cap'       => $economy['users_at_cap'] ?? 0,
                    'total_users_active' => $economy['total_users_active'] ?? 0,
                    'economy_health'     => $economy['economy_health'] ?? 'unknown',
                ],
                'bots'       => $botGlance,
                'schedule'   => $schedule,
                'events'     => $events,
                'alerts'     => array_map(function ($a) {
                    return ['level' => $a['level'], 'message' => $a['message']];
                }, $botAlerts),
                'chart'      => [
                    'labels' => $chartData['labels'] ?? [],
                    'bids'   => $chartData['datasets'][0]['data'] ?? [],
                    'sells'  => $chartData['datasets'][1]['data'] ?? [],
                    'volume' => array_values($dailyVolume),
                ],
                'chart_seed' => $chartSeed,
                'timestamp'  => Carbon::now()->toIso8601String(),
            ],
        ]);
    }

    // ── TERMINAL: USER-FACING LIVE TERMINAL ──

    /**
     * Public terminal endpoint for logged-in users.
     * Entire response cached 5 seconds (handles thousands of concurrent users).
     * Contains ZERO sensitive data. Server-side anonymization and filtering.
     *
     * GET /api/terminal/live?since={ISO_timestamp}
     */
    public function terminalPublic(Request $request): JsonResponse
    {
        $since = $request->input('since');

        // Response-level cache: 5 seconds. Handles scale.
        // The 'since' param is NOT part of the cache key -- feed is always the latest batch.
        $data = Cache::remember('nexus.terminal.public', 5, function () {
            $dashboard      = new DashboardService();
            $botService     = new BotAnalyticsService();
            $sessionMetrics = $dashboard->getSessionMetrics();
            $terminalStats  = $dashboard->getTerminalStats();

            // Session schedule
            $sessionsToday    = $sessionMetrics['sessions_today'] ?? [];
            $sessionStatusMap = [];
            foreach ($sessionsToday as $s) {
                $sessionStatusMap[$s['session_number'] ?? $s->session_number ?? 0] = $s['status'] ?? $s->status ?? 'pending';
            }

            $schedule = [];
            for ($i = 1; $i <= 3; $i++) {
                $time     = NexusConfig::get('session_' . $i . '_start_utc', '00:00');
                $labels   = [1 => 'ASIA-PAC', 2 => 'EUROPE', 3 => 'AMERICAS'];
                $duration = NexusConfig::getInt('session_duration_minutes', 60);
                $endTime  = Carbon::parse($time)->addMinutes($duration)->format('H:i');
                $status   = $sessionStatusMap[$i] ?? 'pending';
                if ($status === 'completed' || $status === 'processing') {
                    $status = 'completed';
                }

                $schedule[] = [
                    'number'   => $i,
                    'time'     => $time,
                    'end_time' => $endTime,
                    'label'    => $labels[$i] ?? 'SESSION ' . $i,
                    'status'   => $status,
                ];
            }

            // Determine next/active session for hero display
            $activeNumber = $sessionMetrics['active_session']['session_number'] ?? null;
            $activeLabel  = $activeNumber ? ($schedule[$activeNumber - 1]['label'] ?? null) : null;
            $nextLabel    = null;
            if (! $activeNumber) {
                foreach ($schedule as $s) {
                    if ($s['status'] === 'pending') {
                        $nextLabel = 'SESSION ' . $s['number'] . ' -- ' . $s['label'];
                        break;
                    }
                }
            }

            // Activity counts
            $uniqueBidders = $sessionMetrics['unique_bidders'] ?? 0;
            $totalBids     = $sessionMetrics['total_bids'] ?? 0;
            $totalVolume   = $sessionMetrics['total_volume'] ?? 0;

            // Trades today: won bids + approved sells
            $today        = Carbon::today();
            $wonBidsCount = Bid::where('type', 'manual_bid')
                ->where('status', 1)
                ->whereDate('bid_time', $today)
                ->count();
            $approvedSellsCount = Sell::where('type', 'manual_sell')
                ->where('status', 1)
                ->whereDate('updated_at', $today)
                ->count();
            $tradesToday = $wonBidsCount + $approvedSellsCount;

            // Platform volume: ALL bids today (AI + manual combined)
            $platformVolume = round((float) Bid::whereDate('bid_time', $today)
                ->sum(DB::raw('amount * quantity')), 2);

            // Current hour activity for chart interpolation
            $currentHourStart     = Carbon::now()->startOfHour();
            $currentHourBids      = (int) Bid::where('bid_time', '>=', $currentHourStart)->count();
            $currentHourSells     = (int) Sell::whereIn('status', [1, 2])
                ->where('updated_at', '>=', $currentHourStart)->count();
            $currentHourActivity  = $currentHourBids + $currentHourSells;
            $currentHourBidVol    = (float) Bid::where('bid_time', '>=', $currentHourStart)
                ->sum(DB::raw('amount * quantity'));
            $currentHourSellVol   = (float) Sell::whereIn('status', [1, 2])
                ->where('updated_at', '>=', $currentHourStart)
                ->sum(DB::raw('amount * quantity'));
            $currentHourVolume    = round($currentHourBidVol + $currentHourSellVol, 2);

            // Cumulative total from chart seed cache
            $seedData        = Cache::get('nexus.terminal.chart_seed');
            $cumulativeTotal = $seedData ? round((float) ($seedData['cumulative_total'] ?? 0), 2) : 0;

            // Feed: anonymized positive events only
            $feed = $this->buildUserFeed($today);

            // Plan showcase (cached 60s)
            $plans = $botService->getPlanShowcase();

            // Charts: 7-day volume + activity
            $weekAgo   = Carbon::now()->subDays(7)->toDateString();
            $chartData = $dashboard->getTradingActivityChart($weekAgo, $today->toDateString());

            $dailyVolume = Bid::where('type', 'manual_bid')
                ->where('bid_time', '>=', Carbon::now()->subDays(7)->startOfDay())
                ->selectRaw('DATE(bid_time) as d, COALESCE(SUM(amount * quantity), 0) as vol')
                ->groupByRaw('DATE(bid_time)')
                ->orderByRaw('DATE(bid_time)')
                ->get();

            $volumeLabels = [];
            $volumeValues = [];
            foreach ($dailyVolume as $row) {
                $volumeLabels[] = Carbon::parse($row->d)->format('M d');
                $volumeValues[] = round((float) $row->vol, 2);
            }

            // Plan profit curves (from sparkline data, normalized to %)
            $planCurves = [];
            foreach ($plans as $p) {
                $key              = strtolower(str_replace(' ', '_', preg_replace('/[^a-zA-Z0-9 ]/', '', $p['name'])));
                $planCurves[$key] = $p['sparkline'] ?? [];
            }

            return [
                'chart_seed' => $dashboard->getTerminalChartSeed(),
                'session'   => [
                    'is_open'            => $sessionMetrics['is_open'],
                    'active_number'      => $activeNumber,
                    'active_label'       => $activeLabel,
                    'seconds_remaining'  => $sessionMetrics['active_session']['seconds_remaining'] ?? 0,
                    'seconds_until_next' => $sessionMetrics['seconds_until_next'] ?? 0,
                    'next_session_label' => $nextLabel,
                    'schedule'           => $schedule,
                ],
                'activity'  => [
                    'trades_today'          => $tradesToday,
                    'users_trading_today'   => $uniqueBidders,
                    'volume_today'          => $totalVolume,
                    'platform_volume_today' => $platformVolume,
                    'current_hour_activity' => $currentHourActivity,
                    'current_hour_volume'   => $currentHourVolume,
                    'cumulative_total'      => $cumulativeTotal,
                    'volume_yesterday'      => round((float) ($terminalStats['deltas']['volume_yesterday'] ?? 0), 2),
                    'status'                => 'operational',
                ],
                'feed'      => $feed,
                'plans'     => $plans,
                'charts'    => [
                    'volume_7d'   => [
                        'labels' => $volumeLabels,
                        'values' => $volumeValues,
                    ],
                    'activity_7d' => [
                        'labels' => $chartData['labels'] ?? [],
                        'bids'   => $chartData['datasets'][0]['data'] ?? [],
                    ],
                    'plan_curves' => $planCurves,
                ],
                'timestamp' => Carbon::now()->toIso8601String(),
            ];
        });

        return response()->json(['success' => true, 'data' => $data]);
    }

    /**
     * Build bot trade feed for admin terminal from actual bids/sells tables.
     * Shows AI bid wins, bid losses, and sell completions with admin-level detail.
     * Runs regardless of NEXUS status -- bots trade through Kernel.php pipeline.
     */
    private function buildAdminBotFeed(?string $since = null): array
    {
        $feed  = [];
        $today = Carbon::today();

        // Recent AI bid wins (processed today)
        $wonQuery = DB::table('bids as b')
            ->join('investments as i', 'b.investment_id', '=', 'i.id')
            ->join('plans as p', 'i.plan_id', '=', 'p.id')
            ->join('users as u', 'b.user_id', '=', 'u.id')
            ->where('b.type', 'ai_bid')
            ->where('b.status', 1)
            ->whereDate('b.updated_at', $today)
            ->orderByDesc('b.updated_at')
            ->limit(30);

        if ($since) {
            $wonQuery->where('b.updated_at', '>', $since);
        }

        $wonBids = $wonQuery->select([
            'b.id as bid_id', 'b.amount', 'b.quantity', 'b.updated_at',
            'i.id as bot_id', 'p.name as plan_name', 'u.username',
        ])->get();

        foreach ($wonBids as $row) {
            $feed[] = [
                'id'         => 'bot_bid_' . $row->bid_id,
                'event'      => 'bot_bid_won',
                'actor_type' => 'bot',
                'actor_id'   => $row->bot_id,
                'user_id'    => null,
                'session_id' => null,
                'sell_id'    => null,
                'bid_id'     => $row->bid_id,
                'context'    => [
                    'plan'     => $row->plan_name,
                    'username' => $row->username,
                    'amount'   => round((float) $row->amount, 2),
                    'quantity' => (int) $row->quantity,
                ],
                'notes'      => 'Bot #' . $row->bot_id . ' (' . $row->plan_name . ') bid won -- $' . number_format((float) $row->amount, 2),
                'created_at' => $row->updated_at,
            ];
        }

        // Recent AI bid losses (processed today)
        $lostQuery = DB::table('bids as b')
            ->join('investments as i', 'b.investment_id', '=', 'i.id')
            ->join('plans as p', 'i.plan_id', '=', 'p.id')
            ->where('b.type', 'ai_bid')
            ->where('b.status', 2)
            ->whereDate('b.updated_at', $today)
            ->orderByDesc('b.updated_at')
            ->limit(15);

        if ($since) {
            $lostQuery->where('b.updated_at', '>', $since);
        }

        $lostBids = $lostQuery->select([
            'b.id as bid_id', 'b.amount', 'b.updated_at',
            'i.id as bot_id', 'p.name as plan_name',
        ])->get();

        foreach ($lostBids as $row) {
            $feed[] = [
                'id'         => 'bot_bid_l_' . $row->bid_id,
                'event'      => 'bot_bid_lost',
                'actor_type' => 'bot',
                'actor_id'   => $row->bot_id,
                'user_id'    => null,
                'session_id' => null,
                'sell_id'    => null,
                'bid_id'     => $row->bid_id,
                'context'    => [
                    'plan'   => $row->plan_name,
                    'amount' => round((float) $row->amount, 2),
                ],
                'notes'      => 'Bot #' . $row->bot_id . ' (' . $row->plan_name . ') bid failed -- $' . number_format((float) $row->amount, 2) . ' refunded',
                'created_at' => $row->updated_at,
            ];
        }

        // Recent AI sell completions (processed today)
        $sellQuery = DB::table('sells as s')
            ->join('investments as i', 's.investment_id', '=', 'i.id')
            ->join('plans as p', 'i.plan_id', '=', 'p.id')
            ->join('users as u', 's.user_id', '=', 'u.id')
            ->where('s.type', 'ai_sell')
            ->where('s.status', 1)
            ->whereDate('s.updated_at', $today)
            ->orderByDesc('s.updated_at')
            ->limit(30);

        if ($since) {
            $sellQuery->where('s.updated_at', '>', $since);
        }

        $completedSells = $sellQuery->select([
            's.id as sell_id', 's.amount', 's.initial_price', 's.updated_at',
            'i.id as bot_id', 'p.name as plan_name', 'u.username',
        ])->get();

        foreach ($completedSells as $row) {
            $profit    = round((float) $row->amount - (float) $row->initial_price, 2);
            $profitPct = (float) $row->initial_price > 0
                ? round($profit / (float) $row->initial_price * 100, 1)
                : 0;

            $feed[] = [
                'id'         => 'bot_sell_' . $row->sell_id,
                'event'      => 'bot_sell_completed',
                'actor_type' => 'bot',
                'actor_id'   => $row->bot_id,
                'user_id'    => null,
                'session_id' => null,
                'sell_id'    => $row->sell_id,
                'bid_id'     => null,
                'context'    => [
                    'plan'       => $row->plan_name,
                    'username'   => $row->username,
                    'amount'     => round((float) $row->amount, 2),
                    'profit'     => $profit,
                    'profit_pct' => $profitPct,
                ],
                'notes'      => 'Bot #' . $row->bot_id . ' (' . $row->plan_name . ') sell completed -- $' . number_format((float) $row->amount, 2) . ' (+' . $profitPct . '%)',
                'created_at' => $row->updated_at,
            ];
        }

        return $feed;
    }

    /**
     * Build anonymized feed for user-facing terminal.
     * Only positive events: won bids, approved sells, successful bot trades.
     * Server-side anonymization: client never receives full usernames.
     */
    private function buildUserFeed(Carbon $today): array
    {
        $feed  = [];
        $limit = 30;

        // Recent won manual bids (anonymized)
        $wonBids = DB::table('bids as b')
            ->join('users as u', 'b.user_id', '=', 'u.id')
            ->leftJoin('products as p', 'b.product_id', '=', 'p.id')
            ->where('b.type', 'manual_bid')
            ->where('b.status', 1)
            ->whereDate('b.bid_time', $today)
            ->orderByDesc('b.bid_time')
            ->limit($limit)
            ->select(['u.username', 'b.amount', 'b.quantity', 'p.name as product_name', 'b.bid_time as event_time'])
            ->get();

        foreach ($wonBids as $row) {
            $feed[] = [
                'time' => Carbon::parse($row->event_time)->format('H:i:s'),
                'type' => 'trade',
                'sort' => $row->event_time,
                'text' => $this->anonymizeName($row->username) . ' won a bid -- $' . number_format((float) $row->amount, 2) . ' on ' . ($row->product_name ?? 'Product'),
            ];
        }

        // Recent approved manual sells (anonymized)
        $approvedSells = DB::table('sells as s')
            ->join('users as u', 's.user_id', '=', 'u.id')
            ->where('s.type', 'manual_sell')
            ->where('s.status', 1)
            ->whereDate('s.updated_at', $today)
            ->orderByDesc('s.updated_at')
            ->limit($limit)
            ->select(['u.username', 's.amount', 's.updated_at as event_time'])
            ->get();

        foreach ($approvedSells as $row) {
            $feed[] = [
                'time' => Carbon::parse($row->event_time)->format('H:i:s'),
                'type' => 'trade',
                'sort' => $row->event_time,
                'text' => $this->anonymizeName($row->username) . ' completed a sell -- $' . number_format((float) $row->amount, 2),
            ];
        }

        // Recent successful bot trades (no individual user data)
        $botBids = DB::table('bids as b')
            ->join('investments as i', 'b.investment_id', '=', 'i.id')
            ->join('plans as p', 'i.plan_id', '=', 'p.id')
            ->where('b.type', '!=', 'manual_bid')
            ->where('b.status', 1)
            ->whereDate('b.bid_time', $today)
            ->whereNotNull('b.investment_id')
            ->whereNotNull('p.nexus_monthly_min')
            ->orderByDesc('b.bid_time')
            ->limit($limit)
            ->select(['p.name as plan_name', 'b.amount', 'b.bid_time as event_time'])
            ->get();

        foreach ($botBids as $row) {
            $feed[] = [
                'time' => Carbon::parse($row->event_time)->format('H:i:s'),
                'type' => 'bot',
                'sort' => $row->event_time,
                'text' => 'AI Trader -- ' . $row->plan_name . ' trade completed -- $' . number_format((float) $row->amount, 2),
            ];
        }

        // Sort all events by time descending, take latest 50
        usort($feed, function ($a, $b) {
            return strcmp($b['sort'], $a['sort']);
        });
        $feed = array_slice($feed, 0, 50);

        // Strip sort key (internal only)
        return array_map(function ($f) {
            unset($f['sort']);
            return $f;
        }, $feed);
    }

    /**
     * Anonymize a username for user-facing display.
     * "Ahmed Store" -> "Ahmed S***"
     * "Merrykeech" -> "Merrykeech"
     * "Christopher's Store" -> "Christopher S***"
     */
    private function anonymizeName(string $username): string
    {
        $parts = explode(' ', trim($username), 2);
        if (count($parts) === 1) {
            // Single name: show first 4 chars + ***
            return strlen($parts[0]) > 4
                ? substr($parts[0], 0, 4) . '***'
                : $parts[0];
        }
        // Multi-word: first name + first letter of second + ***
        return $parts[0] . ' ' . strtoupper(substr($parts[1], 0, 1)) . '***';
    }

    // ── HELPER ──

    /**
     * Stream a CSV response.
     */
    private function streamCsv(string $filename, array $headers, array $data, callable $rowMapper): StreamedResponse
    {
        return response()->streamDownload(function () use ($headers, $data, $rowMapper) {
            $handle = fopen('php://output', 'w');
            fputcsv($handle, $headers);
            foreach ($data as $index => $row) {
                fputcsv($handle, $rowMapper($row, $index));
            }
            fclose($handle);
        }, $filename, [
            'Content-Type'        => 'text/csv',
            'Content-Disposition' => 'attachment; filename="' . $filename . '"',
        ]);
    }
}
