<?php

namespace App\Nexus\Engine;

use App\Models\Bid;
use App\Models\User;
use App\Nexus\Models\NexusSession;
use App\Nexus\Models\NexusUserMetric;
use Carbon\Carbon;

/**
 * NEXUS ENGINE - BidEvaluator
 *
 * Evaluates individual bids during an active session window.
 * 6-step evaluation flow. Returns on first failure.
 *
 * Cap Overhaul (March 20, 2026):
 *   - Step 4 checkMonthlyProfitCap rewritten to use live active_capital
 *     instead of dead monthly_capital (was always 0, cap never fired).
 *   - Capital-weighted effective cap (same log scale as SellEvaluator).
 *   - Lazy reset check (same 30-day rolling window as SellEvaluator).
 *   - No daily/weekly budgets on bid side (those are sell-side pacing).
 *     Bid-side only blocks users who have ALREADY exceeded their monthly cap.
 *
 * @package App\Nexus\Engine
 */
class BidEvaluator
{
    private TradeExecutor $executor;

    public function __construct(TradeExecutor $executor)
    {
        $this->executor = $executor;
    }

    /**
     * Evaluate a single bid.
     */
    public function evaluate(Bid $bid, NexusSession $session): EvaluationResult
    {
        $bid->load(['product', 'product.market', 'user']);

        if (!$bid->product || !$bid->product->market || !$bid->user) {
            return EvaluationResult::rejected('Missing product, market, or user data');
        }

        $user = $bid->user;

        // Step 1: Minimum bid check
        $result = $this->checkMinimumBid($bid);
        if ($result !== null) {
            $this->applyLoss($bid, $user);
            $this->logDecision($bid, $session, $result);
            return $result;
        }

        // Step 2: Session win cap
        $result = $this->checkSessionWinCap($user->id, $session);
        if ($result !== null) {
            $this->applyLoss($bid, $user);
            $this->logDecision($bid, $session, $result);
            return $result;
        }

        // Step 3: Balance check (safety net)
        $totalCost = $bid->amount * $bid->quantity;
        $result = $this->checkBalance($user, $totalCost);
        if ($result !== null) {
            $this->applyLoss($bid, $user);
            $this->logDecision($bid, $session, $result);
            return $result;
        }

        // Step 4: Monthly profit cap
        $result = $this->checkMonthlyProfitCap($user);
        if ($result !== null) {
            $this->applyLoss($bid, $user);
            $this->logDecision($bid, $session, $result);
            return $result;
        }

        // Step 5: First-time bidder guarantee
        $result = $this->checkFirstTimeBidder($user->id, $session);
        if ($result !== null) {
            $this->applyWin($bid, $user, $session);
            $this->logDecision($bid, $session, $result);
            return $result;
        }

        // Step 6: Existing margin formula
        // Matches Kernel.php: margin = product.value - (market.bid_margin% * product.value)
        $market = $bid->product->market;
        $bidMargin = (float) ($market->bid_margin ?? 0);

        if ($bidMargin <= 0) {
            $result = EvaluationResult::rejected('Market does not accept bids');
            $this->applyLoss($bid, $user);
            $this->logDecision($bid, $session, $result);
            return $result;
        }

        $marginThreshold = $bid->product->value - ($bidMargin / 100) * $bid->product->value;

        if ($bid->amount >= $marginThreshold) {
            $result = EvaluationResult::approved('Bid meets margin threshold', [
                'bid_amount'       => (float) $bid->amount,
                'margin_threshold' => $marginThreshold,
                'product_value'    => (float) $bid->product->value,
                'bid_margin_pct'   => $bidMargin,
            ]);
            $this->applyWin($bid, $user, $session);
        } else {
            $result = EvaluationResult::rejected('Bid below margin threshold', [
                'bid_amount'       => (float) $bid->amount,
                'margin_threshold' => $marginThreshold,
                'shortfall'        => round($marginThreshold - $bid->amount, 2),
            ]);
            $this->applyLoss($bid, $user);
        }

        $this->logDecision($bid, $session, $result);
        return $result;
    }

    private function checkMinimumBid(Bid $bid): ?EvaluationResult
    {
        // Per-market bid margin (matches what the user sees on product-details page).
        // Each market has its own bid_margin (e.g., Slickdeals 7%, BrickSeek 9%, Alibaba 10%).
        // The global nexus_config min_bid_percent serves as an absolute floor --
        // no market can go below this, even if its bid_margin is lower.
        $marketBidMargin = (float) ($bid->product->market->bid_margin ?? 0);
        $globalFloor = NexusConfig::getFloat('min_bid_percent', 5.0);
        $minBidPercent = max($marketBidMargin, $globalFloor);

        $minAmount = ($minBidPercent / 100) * $bid->product->value;

        if ($bid->amount < $minAmount) {
            return EvaluationResult::rejected(
                "Bid amount below minimum ({$minBidPercent}% of product value)",
                [
                    'bid_amount'        => (float) $bid->amount,
                    'min_amount'        => $minAmount,
                    'market_bid_margin' => $marketBidMargin,
                    'global_floor'      => $globalFloor,
                ]
            );
        }
        return null;
    }

    private function checkSessionWinCap(int $userId, NexusSession $session): ?EvaluationResult
    {
        // Dynamic per session (same pattern as loss requirement in SellEvaluator).
        // Falls back to flat nexus_config if profile has no value (pre-update sessions).
        $profile = CriteriaRotator::getOrGenerateProfile($session);
        $maxWins = $profile['effective_wins_per_session']
            ?? NexusConfig::getInt('max_wins_per_session', 4);

        $winsThisSession = Bid::where('user_id', $userId)
            ->where('nexus_session_id', $session->id)
            ->where('status', 1)->count();

        if ($winsThisSession >= $maxWins) {
            return EvaluationResult::rejected(
                "Session win cap reached ({$winsThisSession}/{$maxWins})",
                ['wins' => $winsThisSession, 'cap' => $maxWins]
            );
        }
        return null;
    }

    private function checkBalance(User $user, float $totalCost): ?EvaluationResult
    {
        if ($user->balance < 0) {
            return EvaluationResult::rejected('Insufficient balance');
        }
        return null;
    }

    /**
     * Step 4: Monthly Profit Cap Check (REWRITTEN -- Cap Overhaul March 20, 2026)
     *
     * Old: Read dead monthly_capital (always 0), always returned null. Cap never fired.
     * New: Uses live active_capital with capital-weighted effective cap (same formula
     *      as SellEvaluator step4). Includes lazy 30-day reset check.
     *
     * Bid-side check is monthly cap only (no daily/weekly budgets).
     * Purpose: prevent users who have ALREADY exceeded their monthly cap from
     * placing more bids (which would tie up capital in ownerships they cannot
     * profitably sell).
     *
     * @param User $user The bidding user (loaded model)
     * @return EvaluationResult|null null = passed, EvaluationResult = rejected
     */
    private function checkMonthlyProfitCap(User $user): ?EvaluationResult
    {
        $metric = NexusUserMetric::getOrCreate($user->id);

        // Lazy reset check (same logic as SellEvaluator Step 4)
        $capPeriod = NexusConfig::getInt('cap_period_days', 30);

        if ($metric->metrics_reset_at === null) {
            // First-ever interaction. Do not set metrics_reset_at here.
            // Let the sell evaluation (which is the profit event) start the cycle.
            // Bid-side just checks cumulative monthly_profit, which is 0 for new users.
        } elseif (Carbon::now()->diffInDays($metric->metrics_reset_at) >= $capPeriod) {
            // Cycle expired. Reset inline (same as SellEvaluator).
            $metric->update([
                'monthly_profit'   => 0,
                'weekly_profit'    => 0,
                'weekly_budget'    => null,
                'week_started_at'  => null,
                'daily_profit'     => 0,
                'daily_budget'     => null,
                'day_date'         => null,
                'metrics_reset_at' => now(),
            ]);
            $metric->refresh();
        }

        // Calculate live active capital
        $activeCapital = NexusUserMetric::calculateActiveCapital($user);
        if ($activeCapital <= 0) return null;

        // Capital-weighted effective cap (same formula as SellEvaluator)
        $adminMax = NexusConfig::getFloat('monthly_profit_cap_percent', 10.0);
        $floor = max(1.0, $adminMax - 3.0);

        $logMin = log10(50);
        $logMax = log10(1000000);
        $logCap = log10(max($activeCapital, 50));
        $ratio = ($logMax > $logMin) ? ($logCap - $logMin) / ($logMax - $logMin) : 0;
        $ratio = max(0.0, min(1.0, $ratio));

        $effectiveCapPercent = $adminMax - ($ratio * ($adminMax - $floor));
        $monthlyCapAmount = $activeCapital * ($effectiveCapPercent / 100);

        if ($monthlyCapAmount > 0 && (float) $metric->monthly_profit >= $monthlyCapAmount) {
            return EvaluationResult::rejected('Monthly profit cap reached', [
                'current_profit'        => (float) $metric->monthly_profit,
                'cap_amount'            => round($monthlyCapAmount, 2),
                'effective_cap_percent' => round($effectiveCapPercent, 2),
                'active_capital'        => round($activeCapital, 2),
            ]);
        }

        return null;
    }

    private function checkFirstTimeBidder(int $userId, NexusSession $session): ?EvaluationResult
    {
        if (!NexusConfig::getBool('first_timer_guarantee', true)) return null;

        $monthYear = Carbon::now()->format('Y-m');
        $metric = NexusUserMetric::getOrCreate($userId, $monthYear);

        if ($metric->first_bid_win_used) return null;

        $hasEverWon = Bid::where('user_id', $userId)->where('status', 1)->exists();
        if (!$hasEverWon) {
            $metric->update(['first_bid_win_used' => 1]);
            return EvaluationResult::guaranteed('First-time bidder guarantee');
        }
        return null;
    }

    private function applyWin(Bid $bid, User $user, NexusSession $session): void
    {
        $bid->update(['nexus_session_id' => $session->id]);
        $this->executor->executeBidWin($bid);

        $session->increment('total_bids');
        $session->increment('total_bid_cost', $bid->amount * $bid->quantity);
    }

    private function applyLoss(Bid $bid, User $user): void
    {
        $this->executor->executeBidLoss($bid);
    }

    private function logDecision(Bid $bid, NexusSession $session, EvaluationResult $result): void
    {
        NexusAuditLogger::logAlgorithmDecision(
            $session->id, $bid->user_id, 'bid_evaluated', 'bid',
            $bid->id, $result->decision, $result->reason, $result->scoreData
        );
    }
}
