<?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.
 * 4-step evaluation flow. Returns on first failure.
 *
 * Simplified March 22, 2026 (client request):
 *   - Removed balance check (was step 3)
 *   - Removed monthly profit cap check (was step 4)
 *   - Session win cap range changed from 3-5 to 5-10
 *   - Bid evaluation is intentionally less strict than sell evaluation
 *   - Sell evaluation remains the strict gatekeeper
 *
 * @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 (5-10, rotates per session)
        $result = $this->checkSessionWinCap($user->id, $session);
        if ($result !== null) {
            $this->applyLoss($bid, $user);
            $this->logDecision($bid, $session, $result);
            return $result;
        }

        // Step 3: 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 4: Pricing test (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 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
        );
    }
}
