<?php

namespace App\Nexus\Engine;

use App\Models\Investment;
use App\Models\Plan;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;

/**
 * NEXUS ENGINE - BotGovernor
 *
 * Wraps the existing AI bot system with profit governance.
 * Does NOT replace any existing bot logic. Acts as a layer on top,
 * influencing trade outcomes to keep bot profit within plan ranges
 * and producing realistic trading patterns.
 *
 * What BotGovernor controls:
 *   - Daily profit targets per bot (based on plan ranges and capital ratio)
 *   - Success rate (capital-weighted within plan's 85%-99% range)
 *   - Failure rate (higher capital = more failures, drift adjusts)
 *   - Trade range adjustments (effective trade_min/trade_max per day)
 *
 * What BotGovernor does NOT touch:
 *   - The actual bot bid/sell placement logic
 *   - The bot settlement logic (adjustBotBalanceAfterCompletion)
 *   - The bot funding/withdrawal logic
 *
 * Capital-weighted success (Phase 8 update):
 *   High capital bots win toward plan's success_rate_min (85%)
 *   Low capital bots win toward plan's success_rate_max (99%)
 *   Global log scale from $50 to $1,000,000
 *   Drift (HOT/COLD) overrides capital weighting when bot is off-track
 *
 * Called every 5 minutes by the nexus:bot-governor Artisan command.
 *
 * @package App\Nexus\Engine
 */
class BotGovernor
{
    /**
     * Run governance cycle for all active bots.
     * Called every 5 minutes by nexus:bot-governor command.
     *
     * Only runs if NEXUS is active (unless dry-run mode).
     * In dry-run mode, skips the active check so we can test
     * against live bots while NEXUS is still inactive.
     *
     * @param bool $dryRun If true, calculate but do not write. Returns detailed results.
     * @return array Summary with governed/skipped/errors counts (and details in dry-run)
     */
    public function runGovernanceCycle(bool $dryRun = false): array
    {
        $summary = ['governed' => 0, 'skipped' => 0, 'errors' => 0];

        if ($dryRun) {
            $summary['details'] = [];
        }

        // In dry-run mode, skip the active check so we can test while inactive
        if (!$dryRun && !NexusConfig::isNexusActive()) {
            return $summary;
        }

        // Get all active, funded, non-paused bots
        $activeBots = Investment::where('status', 2)
            ->where('funded', 1)
            ->where('is_paused', 0)
            ->where('is_settled', 0)
            ->get();

        foreach ($activeBots as $bot) {
            try {
                if ($dryRun) {
                    $detail = $this->governBotDryRun($bot);
                    if ($detail) {
                        $summary['details'][] = $detail;
                        $summary['governed']++;
                    } else {
                        $summary['skipped']++;
                    }
                } else {
                    $this->governBot($bot);
                    $summary['governed']++;
                }
            } catch (\Throwable $e) {
                Log::error('[BotGovernor] Error governing bot', [
                    'bot_id' => $bot->id,
                    'error'  => $e->getMessage(),
                ]);
                $summary['errors']++;

                if ($dryRun) {
                    $summary['details'][] = [
                        'bot_id' => $bot->id,
                        'error'  => $e->getMessage(),
                    ];
                }
            }
        }

        return $summary;
    }

    /**
     * Dry-run governance for a single bot.
     * Calculates daily target, trade range, and failure rate
     * WITHOUT writing anything to the database.
     *
     * @param Investment $bot The bot to analyze
     * @return array|null Governance analysis, or null if bot should be skipped
     */
    public function governBotDryRun(Investment $bot): ?array
    {
        $plan = Plan::find($bot->plan_id);
        if (!$plan) return null;

        // Skip if plan has no NEXUS ranges configured
        if (is_null($plan->nexus_monthly_min) || is_null($plan->nexus_monthly_max)) {
            return null;
        }

        // Check if bot has expired
        $endDate = Carbon::parse($bot->end_date);
        if (Carbon::now()->greaterThanOrEqualTo($endDate)) {
            return null;
        }

        // Calculate what governance WOULD do
        $dailyTarget = $this->calculateDailyTarget($bot, $plan);
        $tradeRange = $this->getEffectiveTradeRange($bot, $plan, $dailyTarget);
        $failureRate = $this->determineFailureRate($bot, $plan);

        // Calculate progress context
        $amount = (float) $bot->amount;
        $currentProfit = (float) $bot->pfit;
        $buyDate = Carbon::parse($bot->buy_date);
        $daysElapsed = max(0, $buyDate->diffInDays(Carbon::now()));
        $daysTotal = max(1, $buyDate->diffInDays($endDate));
        $daysRemaining = max(1, $daysTotal - $daysElapsed);

        // Target monthly profit (plan-relative capital ratio for target calc)
        $minFund = (float) $plan->min_fund;
        $maxFund = (float) $plan->max_fund;
        $fundRange = $maxFund - $minFund;
        $planCapitalRatio = $fundRange > 0
            ? max(0, min(1, ($amount - $minFund) / $fundRange))
            : 0.5;
        $monthlyMin = (float) $plan->nexus_monthly_min;
        $monthlyMax = (float) $plan->nexus_monthly_max;
        $targetMonthlyPercent = $monthlyMin + ($planCapitalRatio * ($monthlyMax - $monthlyMin));
        $targetMonthlyProfit = ($targetMonthlyPercent / 100) * $amount;

        // Expected profit by now (linear progression)
        $expectedByNow = $targetMonthlyProfit * ($daysElapsed / $daysTotal);
        $drift = $currentProfit - $expectedByNow;

        // Determine track status
        $driftThreshold = $targetMonthlyProfit * 0.10;
        if ($drift > $driftThreshold) {
            $trackStatus = 'HOT';
        } elseif ($drift < -$driftThreshold) {
            $trackStatus = 'COLD';
        } else {
            $trackStatus = 'ON_TRACK';
        }

        // Capital-weighted base rate (before drift adjustment)
        $globalRatio = $this->calculateGlobalCapitalRatio($amount);
        $successMax = (float) ($plan->nexus_success_rate_max ?? 99);
        $successMin = (float) ($plan->nexus_success_rate_min ?? 85);
        $baseSuccess = $successMax - ($globalRatio * ($successMax - $successMin));

        return [
            'bot_id'              => $bot->id,
            'user_id'             => $bot->user_id,
            'plan'                => $plan->name,
            'capital'             => $amount,
            'current_profit'      => round($currentProfit, 2),
            'current_profit_pct'  => $amount > 0 ? round(($currentProfit / $amount) * 100, 2) : 0,
            'target_monthly_pct'  => round($targetMonthlyPercent, 2),
            'target_monthly_usd'  => round($targetMonthlyProfit, 2),
            'expected_by_now'     => round($expectedByNow, 2),
            'drift'               => round($drift, 2),
            'track_status'        => $trackStatus,
            'days_elapsed'        => $daysElapsed,
            'days_remaining'      => $daysRemaining,
            'daily_target'        => round($dailyTarget, 4),
            'trade_range_min'     => $tradeRange['min'],
            'trade_range_max'     => $tradeRange['max'],
            'current_trade_min'   => (float) $bot->trade_min,
            'current_trade_max'   => (float) $bot->trade_max,
            'global_capital_ratio'=> round($globalRatio, 3),
            'capital_base_success'=> round($baseSuccess, 1),
            'failure_rate'        => round($failureRate * 100, 2),
            'success_rate'        => round((1 - $failureRate) * 100, 2),
        ];
    }

    /**
     * Govern a single bot -- calculate daily target and adjust trade range.
     *
     * @param Investment $bot The bot to govern
     * @return void
     */
    public function governBot(Investment $bot): void
    {
        $plan = Plan::find($bot->plan_id);
        if (!$plan) return;

        // Skip if plan has no NEXUS ranges configured
        if (is_null($plan->nexus_monthly_min) || is_null($plan->nexus_monthly_max)) {
            return; // Not configured -- bot runs with existing logic
        }

        // Check if bot has expired
        $endDate = Carbon::parse($bot->end_date);
        if (Carbon::now()->greaterThanOrEqualTo($endDate)) {
            return; // Bot completed -- existing settlement logic handles this
        }

        // Calculate daily target
        $dailyTarget = $this->calculateDailyTarget($bot, $plan);

        // Calculate effective trade range to hit the daily target
        $tradeRange = $this->getEffectiveTradeRange($bot, $plan, $dailyTarget);

        // Update the bot's trade range for today's trades
        $bot->update([
            'trade_min' => $tradeRange['min'],
            'trade_max' => $tradeRange['max'],
        ]);
    }

    /**
     * Determine if the next trade for this bot should succeed.
     *
     * Called by the modified processAiBid() in Kernel.php before
     * marking a bid as won. If this returns false, the trade
     * should be marked as lost instead.
     *
     * @param Investment $bot The bot making the trade
     * @return bool True if trade should succeed, false if it should fail
     */
    public function shouldTradeSucceed(Investment $bot): bool
    {
        if (!NexusConfig::isNexusActive()) {
            return true; // NEXUS inactive -- all trades succeed (existing behavior)
        }

        $plan = Plan::find($bot->plan_id);
        if (!$plan || is_null($plan->nexus_success_rate_min)) {
            return true; // No governance configured
        }

        $failureRate = $this->determineFailureRate($bot, $plan);

        // Generate random float 0.0 to 1.0
        $roll = mt_rand(0, 10000) / 10000;

        // If roll is less than failure rate, the trade fails
        return $roll >= $failureRate;
    }

    /**
     * Calculate the daily profit target for a bot.
     *
     * Uses plan-relative capital ratio (not global) because the daily
     * target should reflect where the bot sits within its own plan's
     * fund range. A $100K bot at the bottom of Host AI ($100K-$1M)
     * targets the plan's monthly min (16%), while a $1M bot targets
     * the monthly max (27%).
     *
     * @param Investment $bot  The bot
     * @param Plan       $plan The bot's plan
     * @return float Daily profit target in dollars
     */
    public function calculateDailyTarget(Investment $bot, Plan $plan): float
    {
        $amount = (float) $bot->amount;
        $minFund = (float) $plan->min_fund;
        $maxFund = (float) $plan->max_fund;

        // Step 1: Capital ratio (where does this bot sit within the plan's fund range)
        $fundRange = $maxFund - $minFund;
        $capitalRatio = $fundRange > 0
            ? max(0, min(1, ($amount - $minFund) / $fundRange))
            : 0.5; // If min == max, use midpoint

        // Step 2: Target monthly percentage (interpolate between plan min and max)
        $monthlyMin = (float) $plan->nexus_monthly_min;
        $monthlyMax = (float) $plan->nexus_monthly_max;
        $targetMonthlyPercent = $monthlyMin + ($capitalRatio * ($monthlyMax - $monthlyMin));

        // Step 3: Target monthly profit in dollars
        $targetMonthlyProfit = ($targetMonthlyPercent / 100) * $amount;

        // Step 4: How much profit remains to reach the target
        $currentProfit = (float) $bot->pfit;
        $remainingProfit = $targetMonthlyProfit - $currentProfit;

        // Step 5: Base daily target
        $buyDate = Carbon::parse($bot->buy_date);
        $endDate = Carbon::parse($bot->end_date);
        $daysElapsed = max(0, $buyDate->diffInDays(Carbon::now()));
        $daysTotal = max(1, $buyDate->diffInDays($endDate));
        $daysRemaining = max(1, $daysTotal - $daysElapsed);

        $dailyBaseTarget = $remainingProfit / $daysRemaining;

        // Step 6: Add organic randomness (0.3 to 1.8 multiplier)
        // Some days nearly zero profit, some days higher to compensate
        $randomMultiplier = 0.3 + (mt_rand(0, 15000) / 10000); // 0.3 to 1.8
        $dailyTarget = $dailyBaseTarget * $randomMultiplier;

        // Step 7: Clamp within plan daily range
        $dailyMin = ((float) ($plan->nexus_daily_min ?? 0)) / 100 * $amount;
        $dailyMax = ((float) ($plan->nexus_daily_max ?? 4)) / 100 * $amount;
        $dailyTarget = max($dailyMin, min($dailyMax, $dailyTarget));

        return round($dailyTarget, 4);
    }

    /**
     * Calculate the global capital ratio for a bot using logarithmic scale.
     *
     * Uses log scale because capital ranges from $50 to $1,000,000.
     * Linear scale would cluster 99% of bots near zero.
     *
     * Log scale distribution:
     *   $500       -> 0.23
     *   $1,000     -> 0.30
     *   $5,000     -> 0.47
     *   $10,000    -> 0.53
     *   $25,000    -> 0.63
     *   $100,000   -> 0.77
     *   $200,000   -> 0.84
     *   $1,000,000 -> 1.00
     *
     * Global boundaries match the outer plan fund limits:
     *   Floor: $50 (DS Amazon Quick View min_fund)
     *   Ceiling: $1,000,000 (Host AI max_fund)
     *
     * @param float $capital The bot's funded amount
     * @return float 0.0 (lowest capital) to 1.0 (highest capital)
     */
    private function calculateGlobalCapitalRatio(float $capital): float
    {
        $globalFloor = 50.0;        // DS Amazon Quick View min_fund
        $globalCeiling = 1000000.0; // Host AI max_fund

        // Clamp capital to global bounds
        $capital = max($globalFloor, min($globalCeiling, $capital));

        // Logarithmic scale: log10($50)=1.7 to log10($1M)=6.0
        $logFloor = log10($globalFloor);
        $logCeiling = log10($globalCeiling);
        $logCapital = log10($capital);

        $range = $logCeiling - $logFloor;
        if ($range <= 0) return 0.5;

        return ($logCapital - $logFloor) / $range;
    }

    /**
     * Determine the current failure rate for a bot.
     *
     * Two-layer system:
     *
     * Layer 1 -- Capital-weighted base rate (global scale):
     *   Low capital (ratio near 0)  -> success near plan's success_rate_max (99%)
     *   High capital (ratio near 1) -> success near plan's success_rate_min (85%)
     *   Uses logarithmic global scale ($50 to $1M)
     *
     * Layer 2 -- Drift adjustment:
     *   COLD (behind target): override to 1% failure regardless of capital.
     *     Target steering takes priority -- bot needs to catch up.
     *   ON_TRACK: use capital-based rate with small random noise.
     *     This is the steady-state behavior.
     *   HOT (ahead of target): amplify capital rate by 1.5x, cap at plan max.
     *     Both capital throttling and target steering align.
     *
     * @param Investment $bot  The bot
     * @param Plan       $plan The bot's plan
     * @return float Failure rate as a decimal (0.01 to ~0.15)
     */
    public function determineFailureRate(Investment $bot, Plan $plan): float
    {
        $amount = (float) $bot->amount;

        // ── Layer 1: Capital-weighted base failure rate ──

        // Global capital ratio (log scale, $50 to $1M)
        $globalRatio = $this->calculateGlobalCapitalRatio($amount);

        // Map to plan's success rate range
        $successMax = (float) ($plan->nexus_success_rate_max ?? 99); // e.g., 99%
        $successMin = (float) ($plan->nexus_success_rate_min ?? 85); // e.g., 85%

        // High capital (ratio near 1) -> success near successMin (85%) -> high failure
        // Low capital (ratio near 0) -> success near successMax (99%) -> low failure
        $baseSuccessRate = $successMax - ($globalRatio * ($successMax - $successMin));
        $baseFailureRate = (100.0 - $baseSuccessRate) / 100.0;

        // Plan's maximum allowable failure rate
        $maxFailureRate = (100.0 - $successMin) / 100.0; // e.g., 0.15 for 85% min

        // ── Layer 2: Drift adjustment ──

        // Use plan-relative ratio for target calculation (unchanged)
        $minFund = (float) $plan->min_fund;
        $maxFund = (float) $plan->max_fund;
        $fundRange = $maxFund - $minFund;
        $planCapitalRatio = $fundRange > 0
            ? max(0, min(1, ($amount - $minFund) / $fundRange))
            : 0.5;

        $monthlyMin = (float) $plan->nexus_monthly_min;
        $monthlyMax = (float) $plan->nexus_monthly_max;
        $targetMonthlyPercent = $monthlyMin + ($planCapitalRatio * ($monthlyMax - $monthlyMin));
        $targetMonthlyProfit = ($targetMonthlyPercent / 100) * $amount;

        $buyDate = Carbon::parse($bot->buy_date);
        $endDate = Carbon::parse($bot->end_date);
        $daysElapsed = max(1, $buyDate->diffInDays(Carbon::now()));
        $daysTotal = max(1, $buyDate->diffInDays($endDate));

        // Where the bot SHOULD be right now (linear progression)
        $expectedProfitByNow = $targetMonthlyProfit * ($daysElapsed / $daysTotal);
        $currentProfit = (float) $bot->pfit;

        // Drift: positive = running hot, negative = running cold
        $drift = $currentProfit - $expectedProfitByNow;
        $driftThreshold = $targetMonthlyProfit * 0.10; // 10% threshold

        if ($drift > $driftThreshold) {
            // Running HOT -- amplify capital-based failure to slow down
            // 1.5x the capital rate, capped at plan's max failure
            return min($maxFailureRate, $baseFailureRate * 1.5);
        } elseif ($drift < -$driftThreshold) {
            // Running COLD -- override capital weighting, minimize failures
            // Target steering takes priority when bot is behind schedule
            return 0.01; // 1% floor
        } else {
            // ON TRACK -- capital-based rate with small random noise (+/- 1.5%)
            $noise = mt_rand(-15, 15) / 1000;
            return max(0.01, min($baseFailureRate + $noise, $maxFailureRate));
        }
    }

    /**
     * Get the effective trade range for a bot today.
     * Adjusts trade_min and trade_max to hit the daily target.
     *
     * @param Investment $bot         The bot
     * @param Plan       $plan        The bot's plan
     * @param float      $dailyTarget Today's profit target in dollars
     * @return array ['min' => float, 'max' => float] as percentages
     */
    public function getEffectiveTradeRange(Investment $bot, Plan $plan, float $dailyTarget): array
    {
        $amount = (float) $bot->amount;
        if ($amount <= 0) {
            return ['min' => (float) $plan->limit_min, 'max' => (float) $plan->limit_max];
        }

        // Convert daily target to a percentage of bot capital
        $targetPercent = ($dailyTarget / $amount) * 100;

        // Set a range around the target
        $rangeSpread = 0.5; // +/- 0.5% spread around target
        $min = max(0.01, $targetPercent - $rangeSpread);
        $max = max($min + 0.1, $targetPercent + $rangeSpread);

        // Clamp to plan's configured daily limits
        $planDailyMin = (float) ($plan->nexus_daily_min ?? 0);
        $planDailyMax = (float) ($plan->nexus_daily_max ?? 4);
        $min = max($planDailyMin, $min);
        $max = min($planDailyMax, $max);

        // Ensure min <= max
        if ($min > $max) {
            $min = $max;
        }

        return ['min' => round($min, 2), 'max' => round($max, 2)];
    }
}
