<?php
/**
 * سودانا — BudgetGate v1
 * Pre-flight budget enforcement for any AI/paid action.
 *
 * Wraps UsageMeter::canAfford() with HTTP-level enforcement.
 *
 * Usage:
 *   $user = SecurityGate::enforce(['auth' => true]);
 *   BudgetGate::enforce($user['id'], 'document', 'business_plan');
 *   // ... only reached if budget OK
 */
require_once __DIR__ . '/UsageMeter.php';
require_once __DIR__ . '/Logger.php';
require_once __DIR__ . '/ReservationEngine.php';

final class BudgetGate {

    /**
     * Throws 402 (Payment Required) or 429 (Too Many Requests) if user cannot afford.
     *
     * @param int    $userId
     * @param string $eventType  'chat'|'document'|'image'|'video'|'booking'
     * @param string $service    e.g. 'business_plan', 'medical', '512x512'
     * @param array  $opts       passed to UsageMeter::canAfford
     */
    public static function enforce(int $userId, string $eventType, string $service, array $opts = []): void {
        $check = UsageMeter::canAfford($userId, $eventType, $service, $opts);
        if ($check['ok']) return;

        // Determine response code based on error type
        $error = $check['error'] ?? 'budget_denied';
        $code  = match($error) {
            'daily_limit_exceeded', 'booking_limit_exceeded' => 429,
            'insufficient_funds'                              => 402,
            default                                           => 403,
        };

        $msg = match($error) {
            'daily_limit_exceeded'   => "تجاوزت حدك اليومي ({$check['used']}/{$check['limit']}). " .
                                        "كل سؤال إضافي = " . number_format($check['overage_sdg'] ?? 500) . " SDG",
            'booking_limit_exceeded' => "تجاوزت حد الحجوزات اليومي ({$check['used']}/{$check['limit']}).",
            'insufficient_funds'     => "رصيدك غير كافٍ. مطلوب: " .
                                        number_format($check['required_sdg'] ?? 0) . " SDG",
            default                  => "العملية غير مسموحة حالياً",
        };

        Logger::info('budget_denied', [
            'user_id' => $userId, 'event' => $eventType, 'service' => $service,
            'error' => $error,
        ]);

        http_response_code($code);
        if (!headers_sent()) header('Content-Type: application/json; charset=utf-8');
        die(json_encode([
            'error'    => $error,
            'message'  => $msg,
            'detail'   => $check,
            'trace_id' => Logger::traceId(),
        ], JSON_UNESCAPED_UNICODE));
    }

    /** Check WITHOUT enforcing — returns the same as UsageMeter::canAfford */
    public static function check(int $userId, string $eventType, string $service, array $opts = []): array {
        return UsageMeter::canAfford($userId, $eventType, $service, $opts);
    }

    /**
     * Wrap an AI callback with full budget+exec+debit cycle.
     * This is the recommended way for ANY new AI integration.
     *
     * Returns the AI result on success, or dies with HTTP error on budget failure.
     */
    public static function metered(int $userId, string $eventType, string $service,
                                    callable $aiCallback, array $opts = []): array {
        // Pre-check (HTTP-level fail-fast)
        self::enforce($userId, $eventType, $service, $opts);

        // Execute through UsageMeter (records tokens, debits wallet)
        $r = UsageMeter::wrap($userId, $eventType, $service, $aiCallback, $opts);

        if (!$r['ok']) {
            Logger::error('metered_failed', ['user_id' => $userId, 'error' => $r['error'] ?? '?']);
            http_response_code(500);
            if (!headers_sent()) header('Content-Type: application/json; charset=utf-8');
            die(json_encode([
                'error'    => $r['error'] ?? 'execution_failed',
                'trace_id' => Logger::traceId(),
            ], JSON_UNESCAPED_UNICODE));
        }

        return $r;
    }

    // ════════════════════════════════════════════════════════════
    //  ESTIMATION-AWARE BUDGET CHECK
    //  CRITICAL: available = wallet + credit - SUM(active_reservations)
    //  This prevents overbooking when user has multiple pending reservations.
    // ════════════════════════════════════════════════════════════

    /**
     * Check if user can afford a full EstimatedPlan.
     * Applies soft reservation accounting per FINAL APPROVAL requirements.
     *
     * @param int   $userId
     * @param array $plan   From TaskEstimator::analyze()
     * @return array {
     *   ok: bool,
     *   required_sdg: int,
     *   available_sdg: int,
     *   shortfall_sdg: int,
     *   breakdown: {wallet, credit, reserved, available},
     *   can_partial: bool,
     *   error?: string,
     *   suggestions?: array
     * }
     */
    public static function canAffordEstimate(int $userId, array $plan): array {
        require_once __DIR__ . '/UsageMeter.php';
        require_once __DIR__ . '/TaskEstimator.php';

        $requiredSdg = (int)($plan['cost']['total_sdg'] ?? 0);

        // ── 1. Check max active reservations ─────────────────
        $maxActive = (int)\PricingEngine::getSetting('max_active_reservations', 3);
        $activeCount = ReservationEngine::countActive($userId);
        if ($activeCount >= $maxActive) {
            return [
                'ok'           => false,
                'error'        => 'too_many_reservations',
                'required_sdg' => $requiredSdg,
                'available_sdg'=> 0,
                'shortfall_sdg'=> $requiredSdg,
                'breakdown'    => [],
            ];
        }

        // ── 2. Get raw balances ───────────────────────────────
        $walletBalance = UsageMeter::getWalletBalance($userId);

        $creditRemaining = 0;
        try {
            $st = db()->prepare(
                "SELECT remaining_sdg FROM subscription_credits
                 WHERE user_id=? AND period_month=MONTH(NOW()) AND period_year=YEAR(NOW())
                 LIMIT 1"
            );
            $st->execute([$userId]);
            $creditRemaining = (int)($st->fetchColumn() ?? 0);
        } catch (\Throwable $e) {}

        // ── 3. SOFT RESERVATION ACCOUNTING ───────────────────
        // CRITICAL: subtract active reservations to prevent overbooking
        $alreadyReserved = ReservationEngine::sumActiveReserved($userId);

        // available = wallet + credit - active_reservations
        $availableSdg = max(0, ($walletBalance + $creditRemaining) - $alreadyReserved);

        // ── 4. Decision ───────────────────────────────────────
        $ok          = $availableSdg >= $requiredSdg;
        $shortfall   = $ok ? 0 : ($requiredSdg - $availableSdg);

        $result = [
            'ok'            => $ok,
            'required_sdg'  => $requiredSdg,
            'available_sdg' => $availableSdg,
            'shortfall_sdg' => $shortfall,
            'breakdown'     => [
                'wallet'          => $walletBalance,
                'credit'          => $creditRemaining,
                'reserved'        => $alreadyReserved,
                'available'       => $availableSdg,
            ],
            'can_partial'   => $availableSdg > 0 && !$ok,
        ];

        // Attach suggestions on insufficient funds
        if (!$ok) {
            $result['error']       = 'insufficient_funds';
            $result['suggestions'] = TaskEstimator::buildSuggestions($plan, $availableSdg);
        }

        return $result;
    }

    /**
     * Charge overage when actual > reserved (called by TaskRunner).
     * Returns true if charged, false if insufficient funds.
     */
    public static function chargeOverage(int $userId, int $overageSdg, string $reservationId): bool {
        require_once __DIR__ . '/UsageMeter.php';
        if ($overageSdg <= 0) return true;

        $plan = UsageMeter::getUserPlan($userId);
        $tolerancePct = (int)\PricingEngine::getSetting("overage_tolerance_pct_$plan", 3);

        $debit = UsageMeter::debit($userId, $overageSdg, 'overage', "overage:$reservationId");
        if (!$debit['ok']) {
            Logger::warn('overage_charge_failed', [
                'user_id'        => $userId,
                'overage_sdg'    => $overageSdg,
                'reservation_id' => $reservationId,
            ]);
        }
        return $debit['ok'];
    }

}