<?php

namespace App\Nexus\Engine;

use App\Nexus\Models\NexusSession;
use Carbon\Carbon;

/**
 * NEXUS ENGINE - SessionController
 *
 * Manages the lifecycle of trading sessions.
 * Called every minute by nexus:session-check.
 *
 * Deployed schema mapping:
 *   status: pending -> active -> processing -> completed
 *   open_time  = actual_start
 *   close_time = actual_end
 *   profile    = criteria_order (JSON cast to array)
 *   Has: session_date, scheduled_start, total_bid_cost, total_sell_value
 *
 * @package App\Nexus\Engine
 */
class SessionController
{
    /**
     * Check if any session should open now, and open it.
     */
    public function checkAndOpenSessions(): void
    {
        if ($this->getActiveSession() !== null) {
            return;
        }

        $now   = Carbon::now('UTC');
        $today = $now->toDateString();

        for ($sessionNumber = 1; $sessionNumber <= 3; $sessionNumber++) {
            $configuredTime = NexusConfig::get('session_' . $sessionNumber . '_start_utc');
            if (empty($configuredTime)) continue;

            if ($this->shouldSessionOpen($sessionNumber, $configuredTime, $now, $today)) {
                $scheduledStart = Carbon::parse($today . ' ' . $configuredTime, 'UTC');
                $this->openSession($sessionNumber, $scheduledStart, $today);
                return;
            }
        }
    }

    /**
     * Check if any open session should close, and close it.
     */
    public function checkAndCloseSessions(): void
    {
        $session = $this->getActiveSession();
        if ($session === null) return;

        // Use app timezone for lifecycle -- Eloquent returns actual_start in app timezone.
        // Comparing in the same timezone gives correct duration.
        $now = Carbon::now();
        $durationMinutes = NexusConfig::getInt('session_duration_minutes', 60);
        $startTime = Carbon::parse($session->actual_start);
        $shouldClose = $now->diffInSeconds($startTime) >= ($durationMinutes * 60);

        if ($shouldClose) {
            $this->closeSession($session);
        }
    }

    /**
     * Get the currently active session, if any.
     */
    public function getActiveSession(): ?NexusSession
    {
        return NexusSession::where('status', NexusSession::STATUS_ACTIVE)->first();
    }

    /**
     * Check if any session is currently active.
     */
    public function isSessionOpen(): bool
    {
        return $this->getActiveSession() !== null;
    }

    /**
     * Get the next upcoming session start time.
     */
    public function getNextSessionTime(): Carbon
    {
        $now   = Carbon::now('UTC');
        $today = $now->toDateString();
        $candidates = [];

        for ($day = 0; $day <= 1; $day++) {
            $date = Carbon::parse($today, 'UTC')->addDays($day)->toDateString();
            for ($num = 1; $num <= 3; $num++) {
                $time = NexusConfig::get('session_' . $num . '_start_utc');
                if (empty($time)) continue;

                $sessionTime = Carbon::parse($date . ' ' . $time, 'UTC');
                if ($sessionTime->greaterThan($now)) {
                    $candidates[] = $sessionTime;
                }
            }
        }

        if (empty($candidates)) {
            $firstTime = NexusConfig::get('session_1_start_utc', '01:00');
            return Carbon::parse(Carbon::tomorrow('UTC')->toDateString() . ' ' . $firstTime, 'UTC');
        }

        usort($candidates, fn(Carbon $a, Carbon $b) => $a->timestamp - $b->timestamp);
        return $candidates[0];
    }

    /**
     * Seconds until next session opens.
     */
    public function getSecondsUntilNextSession(): int
    {
        return max(0, Carbon::now('UTC')->diffInSeconds($this->getNextSessionTime(), false));
    }

    /**
     * Seconds remaining in current active session.
     */
    public function getSecondsRemainingInSession(): int
    {
        $session = $this->getActiveSession();
        if ($session === null) return 0;

        $durationMinutes = NexusConfig::getInt('session_duration_minutes', 60);
        // Eloquent returns actual_start in app timezone -- stay in app timezone
        $closeTime = Carbon::parse($session->actual_start)->addMinutes($durationMinutes);

        return max(0, Carbon::now()->diffInSeconds($closeTime, false));
    }

    /**
     * Should this session open right now?
     * Must be within 1 minute of scheduled time AND not already opened today.
     */
    private function shouldSessionOpen(int $sessionNumber, string $configuredTime, Carbon $now, string $today): bool
    {
        $scheduledTime = Carbon::parse($today . ' ' . $configuredTime, 'UTC');
        $durationMinutes = NexusConfig::getInt('session_duration_minutes', 60);
        $endTime = $scheduledTime->copy()->addMinutes($durationMinutes);

        // Open if we are within the session window (with 90s early tolerance for cron timing)
        // This ensures if cron misses the exact minute, session still opens during the window
        if ($now->lessThan($scheduledTime->copy()->subSeconds(90))) return false;
        if ($now->greaterThanOrEqualTo($endTime)) return false;

        // Check if this session number already exists today
        $existsToday = NexusSession::where('session_number', $sessionNumber)
            ->where('session_date', $today)
            ->exists();

        return !$existsToday;
    }

    /**
     * Open a new session.
     */
    private function openSession(int $sessionNumber, Carbon $scheduledStart, string $today): NexusSession
    {
        // Use app timezone for actual_start -- Eloquent reads it back in app timezone.
        // Storing and reading in the same timezone ensures correct duration calculations.
        $now = Carbon::now();

        $session = NexusSession::create([
            'session_number'        => $sessionNumber,
            'session_date'          => $today,
            'scheduled_start'       => $scheduledStart->copy()->setTimezone(config('app.timezone')),
            'actual_start'          => $now,
            'actual_end'            => null,
            'status'                => NexusSession::STATUS_ACTIVE,
            'total_bid_cost'        => 0,
            'total_sell_value'      => 0,
            'win_rate_target'       => null,
            'win_rate_actual'       => null,
            'total_bids'            => 0,
            'total_sells_evaluated' => 0,
            'total_sells_approved'  => 0,
            'total_sells_rejected'  => 0,
            'criteria_order'        => null,
            'session_notes'         => null,
        ]);

        // Generate and store the criteria profile
        CriteriaRotator::getOrGenerateProfile($session);

        NexusAuditLogger::logSessionEvent($session->id, 'session_opened', [
            'session_number'  => $sessionNumber,
            'session_date'    => $today,
            'scheduled_start' => $scheduledStart->toIso8601String(),
            'actual_start'    => $now->toIso8601String(),
            'profile'         => $session->fresh()->criteria_order,
        ]);

        return $session;
    }

    /**
     * Close an active session.
     * Sets status to 'processing'. Sell evaluation runs separately
     * and marks it 'completed' when done.
     */
    private function closeSession(NexusSession $session): void
    {
        $now = Carbon::now();

        $session->update([
            'status'     => NexusSession::STATUS_PROCESSING,
            'actual_end' => $now,
        ]);

        NexusAuditLogger::logSessionEvent($session->id, 'session_closed', [
            'session_number'   => $session->session_number,
            'actual_end'       => $now->toIso8601String(),
            'total_bids'       => $session->total_bids,
            'total_bid_cost'   => (float) $session->total_bid_cost,
            'total_sell_value' => (float) $session->total_sell_value,
            'duration_seconds' => Carbon::parse($session->actual_start)->diffInSeconds($now),
        ]);
    }
}
