<?php

namespace App\Nexus\Engine;

use App\Nexus\Models\NexusSession;

/**
 * NEXUS ENGINE - CriteriaRotator
 *
 * Generates deterministic per-session weight profiles.
 * Same session ID always produces the same profile.
 *
 * Deployed schema uses:
 *   nexus_sessions.criteria_order (JSON) -- not session_profile
 *   nexus_sessions status values: pending, active, processing, completed, cancelled
 *
 * Updated March 20, 2026: Added effective_wins_per_session (dynamic per session).
 *   New mt_rand call added AFTER generateStepOrder() to preserve determinism
 *   of all existing profile values. Existing cached sessions are unaffected.
 *
 * @package App\Nexus\Engine
 */
class CriteriaRotator
{
    /**
     * Generate a session profile from the session ID as seed.
     * All values deterministic from the seed. Same ID = same profile.
     *
     * @param int $sessionId nexus_sessions.id used as seed
     * @return array The complete session profile
     */
    public static function generateProfile(int $sessionId): array
    {
        // Seed PRNG for determinism. DO NOT reorder any mt_rand calls below.
        mt_srand($sessionId);

        // Generate raw weights within defined ranges
        $lossRaw     = self::seededFloat(0.30, 0.60);
        $capitalRaw  = self::seededFloat(0.10, 0.30);
        $marginRaw   = self::seededFloat(0.10, 0.30);
        $orderingRaw = self::seededFloat(0.05, 0.15);
        $noiseRaw    = self::seededFloat(0.05, 0.15);

        // Normalize to sum = 1.0
        $total = $lossRaw + $capitalRaw + $marginRaw + $orderingRaw + $noiseRaw;
        $lossWeight     = round($lossRaw / $total, 4);
        $capitalWeight  = round($capitalRaw / $total, 4);
        $marginWeight   = round($marginRaw / $total, 4);
        $orderingWeight = round($orderingRaw / $total, 4);
        $noiseFactor    = round(1.0 - $lossWeight - $capitalWeight - $marginWeight - $orderingWeight, 4);

        // Win rate variance: -5.0 to +5.0 percentage points
        $winRateVariance = round(self::seededFloat(-5.0, 5.0), 2);

        // Effective loss requirement (within admin-set range)
        $lossRange = NexusConfig::get('loss_requirement_min');
        $lossMin = $lossRange !== null ? (int) $lossRange : 5;
        $lossRange = NexusConfig::get('loss_requirement_max');
        $lossMax = $lossRange !== null ? (int) $lossRange : 20;
        $effectiveLossReq = self::seededInt($lossMin, $lossMax);

        // Noise seed for winner selection randomness
        $noiseSeed = mt_rand(100000, 999999);

        // Shuffle evaluation steps 3-8 (Fisher-Yates)
        $rotatedSteps = self::generateStepOrder();

        // ── POST-LAUNCH ADDITION (March 20, 2026) ──
        // Dynamic wins per session (same pattern as loss requirement).
        // Placed AFTER generateStepOrder() to preserve determinism of all
        // values above. Existing cached session profiles are unaffected
        // because they are read from nexus_sessions.criteria_order JSON.
        $winsMinConfig = NexusConfig::get('max_wins_per_session_min');
        $winsMin = $winsMinConfig !== null ? (int) $winsMinConfig : 3;
        $winsMaxConfig = NexusConfig::get('max_wins_per_session_max');
        $winsMax = $winsMaxConfig !== null ? (int) $winsMaxConfig : 5;
        $effectiveWinsPerSession = self::seededInt($winsMin, $winsMax);

        // Reset PRNG
        mt_srand();

        return [
            'loss_weight'                => $lossWeight,
            'capital_weight'             => $capitalWeight,
            'margin_weight'              => $marginWeight,
            'ordering_weight'            => $orderingWeight,
            'noise_factor'               => $noiseFactor,
            'win_rate_variance'          => $winRateVariance,
            'effective_loss_requirement' => $effectiveLossReq,
            'noise_seed'                 => $noiseSeed,
            'step_order'                 => $rotatedSteps,
            'effective_wins_per_session' => $effectiveWinsPerSession,
        ];
    }

    /**
     * Retrieve or generate the profile for an existing session.
     * Profile stored in nexus_sessions.criteria_order (JSON).
     *
     * @param NexusSession $session
     * @return array
     */
    public static function getOrGenerateProfile(NexusSession $session): array
    {
        // criteria_order is cast to array by the model
        if (!empty($session->criteria_order)) {
            return $session->criteria_order;
        }

        $profile = self::generateProfile($session->id);
        $session->update(['criteria_order' => $profile]);

        return $profile;
    }

    /**
     * Shuffle steps 3-8 using seeded PRNG (Fisher-Yates).
     */
    private static function generateStepOrder(): array
    {
        $steps = [3, 4, 5, 6, 7, 8];
        for ($i = count($steps) - 1; $i > 0; $i--) {
            $j = mt_rand(0, $i);
            $temp = $steps[$i];
            $steps[$i] = $steps[$j];
            $steps[$j] = $temp;
        }
        return $steps;
    }

    private static function seededFloat(float $min, float $max): float
    {
        $random = mt_rand(0, 10000) / 10000;
        return $min + ($random * ($max - $min));
    }

    private static function seededInt(int $min, int $max): int
    {
        return mt_rand($min, $max);
    }
}
