<?php
/**
 * سودانا — UsageMeter v1
 * Middleware يلفّ كل استدعاء AI:
 *   1. قبل: تحقق من الحد اليومي + رصيد المحفظة
 *   2. تنفيذ الاستدعاء (callback)
 *   3. بعد: حساب التكلفة الحقيقية + خصم تلقائي + تسجيل
 *
 * Usage:
 *   $r = UsageMeter::wrap($userId, 'document', 'business_plan', function() {
 *       return AIClient::call($prompt, $sys, ['max_tokens'=>3000]);
 *   });
 *   if (!$r['ok']) { return error($r['error']); }
 *   $text = $r['result']['text'];
 */
class UsageMeter {

    /**
     * Wraps an AI call with budget check + cost tracking + auto-debit
     *
     * @param int      $userId
     * @param string   $eventType  e.g. 'chat', 'document', 'image', 'video'
     * @param string   $service    e.g. 'business_plan', 'medical', 'legal'
     * @param callable $aiCallback returns AIClient::call() result array
     * @param array    $opts       ['skip_debit'=>bool, 'fixed_price_sdg'=>int]
     * @return array {
     *   ok: bool,
     *   error?: string,
     *   result: array (AI result),
     *   cost_usd: float,
     *   cost_sdg: int,
     *   tokens_in: int,
     *   tokens_out: int,
     *   wallet_after: int
     * }
     */
    public static function wrap(int $userId, string $eventType, string $service, callable $aiCallback, array $opts = []): array {
        require_once __DIR__ . '/PricingEngine.php';

        // ── 1. Budget pre-check ─────────────────────────────────
        $check = self::canAfford($userId, $eventType, $service, $opts);
        if (!$check['ok']) {
            return ['ok' => false, 'error' => $check['error'], 'detail' => $check];
        }

        // ── 2. Execute AI call ──────────────────────────────────
        $startMs = microtime(true);
        try {
            $result = $aiCallback();
        } catch (Throwable $e) {
            return ['ok' => false, 'error' => 'ai_call_failed: ' . $e->getMessage()];
        }
        $latency = (int)((microtime(true) - $startMs) * 1000);

        if (empty($result['text']) && empty($result['content'])) {
            return ['ok' => false, 'error' => 'ai_empty_response'];
        }

        // ── 3. Calculate real cost ─────────────────────────────
        $tokensIn  = (int)($result['tokens_in']  ?? 0);
        $tokensOut = (int)($result['tokens_out'] ?? 0);
        $costUsd   = (float)($result['cost_usd'] ?? 0);
        $rate      = PricingEngine::getRate();
        $realCostSdg = (int)round($costUsd * $rate);

        // Determine charge to user (fixed price OR computed)
        $chargeSdg = $opts['fixed_price_sdg']
            ?? self::resolveChargeForUser($userId, $eventType, $service);

        // ── 4. Debit wallet/credit ──────────────────────────────
        $debited = ['source' => 'none', 'wallet_after' => 0];
        if (empty($opts['skip_debit']) && $chargeSdg > 0) {
            $debited = self::debit($userId, $chargeSdg, $eventType, $service);
            if (!$debited['ok']) {
                // AI call already happened — log but warn
                error_log("[UsageMeter] AI call succeeded but debit failed for user $userId: {$debited['error']}");
            }
        }

        // ── 5. Log to ai_usage with real numbers ────────────────
        try {
            db()->prepare(
                "INSERT INTO ai_usage
                 (user_id, event_type, tokens_used, tokens_in, tokens_out,
                  cost_usd, cost_sdg, service, provider, created_at)
                 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())"
            )->execute([
                $userId, $eventType,
                $tokensIn + $tokensOut, $tokensIn, $tokensOut,
                $costUsd, $realCostSdg, $service,
                $result['model_used'] ?? $result['provider'] ?? null,
            ]);
        } catch (Throwable $e) {
            error_log("[UsageMeter::log] " . $e->getMessage());
        }

        return [
            'ok'           => true,
            'result'       => $result,
            'tokens_in'    => $tokensIn,
            'tokens_out'   => $tokensOut,
            'cost_usd'     => $costUsd,
            'cost_sdg_api' => $realCostSdg,    // التكلفة الحقيقية لنا
            'charged_sdg'  => $chargeSdg,       // ما خصمناه من المستخدم
            'margin_sdg'   => $chargeSdg - $realCostSdg,
            'wallet_after' => $debited['wallet_after'] ?? 0,
            'source'       => $debited['source'] ?? 'none',
            'latency_ms'   => $latency,
        ];
    }

    /** Check if user can afford this AI call */
    public static function canAfford(int $userId, string $eventType, string $service, array $opts = []): array {
        // Get user plan
        $plan = self::getUserPlan($userId);

        // Daily limit (chat/general)
        if ($eventType === 'chat' || $eventType === 'general') {
            $limit = self::getDailyLimit($plan, 'ai');
            $used  = self::countToday($userId, ['chat', 'general', 'gateway_call']);
            if ($used >= $limit) {
                $overageSdg = (int)PricingEngine::getSetting('ai_overage_charge_sdg', 500);
                $hasFunds   = self::checkFunds($userId, $overageSdg);
                if (!$hasFunds) {
                    return ['ok' => false, 'error' => 'daily_limit_exceeded',
                            'limit' => $limit, 'used' => $used,
                            'overage_sdg' => $overageSdg];
                }
            }
        }

        // Booking limit
        if ($eventType === 'booking') {
            $limit = self::getDailyLimit($plan, 'booking');
            $used  = self::countBookingsToday($userId);
            if ($used >= $limit) {
                return ['ok' => false, 'error' => 'booking_limit_exceeded',
                        'limit' => $limit, 'used' => $used];
            }
        }

        // Document/Image/Video — needs wallet or credit
        if (in_array($eventType, ['document', 'image', 'video'], true)) {
            $price = self::resolveChargeForUser($userId, $eventType, $service);
            $hasFunds = self::checkFunds($userId, $price);
            if (!$hasFunds) {
                return ['ok' => false, 'error' => 'insufficient_funds',
                        'required_sdg' => $price];
            }
        }

        return ['ok' => true];
    }

    /** Get user's plan key from users table */
    public static function getUserPlan(int $userId): string {
        try {
            $st = db()->prepare("SELECT plan_key, subscription_tier FROM users WHERE id=? LIMIT 1");
            $st->execute([$userId]);
            $row = $st->fetch();
            return $row['plan_key'] ?? $row['subscription_tier'] ?? 'free';
        } catch (Throwable $e) { return 'free'; }
    }

    /** Read daily limit from platform_settings */
    public static function getDailyLimit(string $plan, string $type = 'ai'): int {
        $key = $type === 'ai' ? "ai_daily_limit_$plan" : "booking_limit_$plan";
        $defaults = [
            'ai_daily_limit_free' => 20, 'ai_daily_limit_plus' => 60,
            'ai_daily_limit_pro'  => 200,'ai_daily_limit_corp' => 500,
            'booking_limit_free'  => 2,  'booking_limit_plus'  => 5,
            'booking_limit_pro'   => 20, 'booking_limit_corp'  => 100,
        ];
        return (int)PricingEngine::getSetting($key, $defaults[$key] ?? 20);
    }

    /** Count today's AI calls for user */
    public static function countToday(int $userId, array $eventTypes): int {
        try {
            $placeholders = implode(',', array_fill(0, count($eventTypes), '?'));
            $st = db()->prepare(
                "SELECT COUNT(*) FROM ai_usage
                 WHERE user_id=? AND DATE(created_at)=CURDATE()
                   AND event_type IN ($placeholders)"
            );
            $st->execute(array_merge([$userId], $eventTypes));
            return (int)$st->fetchColumn();
        } catch (Throwable $e) { return 0; }
    }

    public static function countBookingsToday(int $userId): int {
        try {
            $st = db()->prepare(
                "SELECT COUNT(*) FROM bookings
                 WHERE user_id=? AND DATE(created_at)=CURDATE()"
            );
            $st->execute([$userId]);
            return (int)$st->fetchColumn();
        } catch (Throwable $e) { return 0; }
    }

    /** Resolve actual charge for this user given plan + service */
    public static function resolveChargeForUser(int $userId, string $eventType, string $service): int {
        $plan = self::getUserPlan($userId);
        if ($eventType === 'document') {
            return PricingEngine::getDocumentPrice($service, $plan);
        }
        if ($eventType === 'image') {
            return self::getImagePrice($service, $plan);
        }
        if ($eventType === 'video') {
            return self::getVideoPrice($service, $plan);
        }
        // chat overage
        return (int)PricingEngine::getSetting('ai_overage_charge_sdg', 500);
    }

    public static function getImagePrice(string $sizeKey, string $plan): int {
        try {
            $col = "price_{$plan}_sdg";
            if (!in_array($plan, ['free','plus','pro'], true)) $col = 'price_free_sdg';
            $st = db()->prepare("SELECT $col FROM image_prices WHERE size_key=? AND is_active=1 LIMIT 1");
            $st->execute([$sizeKey]);
            return (int)($st->fetchColumn() ?? 0);
        } catch (Throwable $e) { return 0; }
    }

    public static function getVideoPrice(string $durationKey, string $plan): int {
        try {
            $col = "price_{$plan}_sdg";
            if (!in_array($plan, ['free','plus','pro'], true)) $col = 'price_free_sdg';
            $st = db()->prepare("SELECT $col FROM video_prices WHERE duration_key=? AND is_active=1 LIMIT 1");
            $st->execute([$durationKey]);
            return (int)($st->fetchColumn() ?? 0);
        } catch (Throwable $e) { return 0; }
    }

    /** Check if user has wallet+credit ≥ amount */
    public static function checkFunds(int $userId, int $amountSdg): bool {
        if ($amountSdg <= 0) return true;
        try {
            // 1. Check subscription credit first
            $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]);
            $credit = (int)($st->fetchColumn() ?? 0);
            if ($credit >= $amountSdg) return true;

            // 2. Fall back to wallet
            $st = db()->prepare("SELECT balance FROM wallet WHERE user_id=? LIMIT 1");
            $st->execute([$userId]);
            $wallet = (int)($st->fetchColumn() ?? 0);
            return ($credit + $wallet) >= $amountSdg;
        } catch (Throwable $e) { return false; }
    }

    /** Debit credit first, then wallet */
    public static function debit(int $userId, int $amountSdg, string $eventType, string $service): array {
        if ($amountSdg <= 0) {
            return ['ok'=>true, 'source'=>'none', 'wallet_after'=>self::getWalletBalance($userId)];
        }
        try {
            db()->beginTransaction();

            // Try credit first
            $st = db()->prepare(
                "SELECT id, remaining_sdg FROM subscription_credits
                 WHERE user_id=? AND period_month=MONTH(NOW()) AND period_year=YEAR(NOW())
                 LIMIT 1 FOR UPDATE"
            );
            $st->execute([$userId]);
            $credit = $st->fetch();

            $remaining = $amountSdg;
            $usedCredit = 0;
            $usedWallet = 0;

            if ($credit && $credit['remaining_sdg'] > 0) {
                $usedCredit = min($remaining, (int)$credit['remaining_sdg']);
                db()->prepare(
                    "UPDATE subscription_credits
                     SET used_sdg=used_sdg+?, remaining_sdg=remaining_sdg-?
                     WHERE id=?"
                )->execute([$usedCredit, $usedCredit, $credit['id']]);
                $remaining -= $usedCredit;
            }

            if ($remaining > 0) {
                // Debit wallet
                $w = db()->prepare("SELECT balance FROM wallet WHERE user_id=? LIMIT 1 FOR UPDATE");
                $w->execute([$userId]);
                $balance = (int)($w->fetchColumn() ?? 0);

                if ($balance < $remaining) {
                    db()->rollBack();
                    return ['ok'=>false, 'error'=>'insufficient_funds_at_debit'];
                }
                $usedWallet = $remaining;
                db()->prepare("UPDATE wallet SET balance=balance-? WHERE user_id=?")
                   ->execute([$remaining, $userId]);
            }

            // Log transaction
            db()->prepare(
                "INSERT INTO wallet_transactions
                 (user_id, amount_sdg, type, reason, created_at)
                 VALUES (?, ?, 'debit', ?, NOW())"
            )->execute([$userId, $amountSdg, "$eventType:$service"]);

            db()->commit();

            $source = $usedCredit && $usedWallet ? 'mixed'
                    : ($usedCredit ? 'credit' : 'wallet');
            return [
                'ok' => true,
                'source' => $source,
                'used_credit' => $usedCredit,
                'used_wallet' => $usedWallet,
                'wallet_after' => self::getWalletBalance($userId),
            ];
        } catch (Throwable $e) {
            db()->rollBack();
            error_log("[UsageMeter::debit] " . $e->getMessage());
            return ['ok'=>false, 'error'=>$e->getMessage()];
        }
    }

    public static function getWalletBalance(int $userId): int {
        try {
            $st = db()->prepare("SELECT balance FROM wallet WHERE user_id=? LIMIT 1");
            $st->execute([$userId]);
            return (int)($st->fetchColumn() ?? 0);
        } catch (Throwable $e) { return 0; }
    }

    /** Get usage summary for user (used by dashboard) */
    public static function getUserSummary(int $userId, int $days = 30): array {
        try {
            $st = db()->prepare(
                "SELECT
                   SUM(CASE WHEN DATE(created_at)=CURDATE() AND event_type IN ('chat','general','gateway_call') THEN 1 ELSE 0 END) as ai_today,
                   SUM(CASE WHEN DATE(created_at)>=DATE_SUB(NOW(), INTERVAL ? DAY) THEN tokens_used ELSE 0 END) as tokens_period,
                   SUM(CASE WHEN DATE(created_at)>=DATE_SUB(NOW(), INTERVAL ? DAY) THEN cost_sdg ELSE 0 END) as spent_sdg
                 FROM ai_usage WHERE user_id=?"
            );
            $st->execute([$days, $days, $userId]);
            $row = $st->fetch() ?: [];
            return [
                'ai_today'      => (int)($row['ai_today'] ?? 0),
                'tokens_period' => (int)($row['tokens_period'] ?? 0),
                'spent_sdg'     => (int)($row['spent_sdg'] ?? 0),
                'days'          => $days,
            ];
        } catch (Throwable $e) {
            return ['ai_today'=>0, 'tokens_period'=>0, 'spent_sdg'=>0, 'days'=>$days];
        }
    }

    // ════════════════════════════════════════════════════════════
    //  RESERVATION-AWARE DEBIT & REFUND
    // ════════════════════════════════════════════════════════════

    /**
     * Debit the full reserved amount when a reservation is activated.
     * Uses existing debit() under the hood with reservation linkage.
     */
    public static function debitReservation(int $userId, int $amountSdg, string $reservationId): array {
        if ($amountSdg <= 0) return ['ok'=>true, 'source'=>'none', 'wallet_after'=>self::getWalletBalance($userId)];
        return self::debit($userId, $amountSdg, 'reservation', $reservationId);
    }

    /**
     * Refund a portion of the reserved amount back to user.
     * Used by ReservationEngine::complete() when actual < reserved.
     *
     * @param int    $userId
     * @param int    $amountSdg   Amount to return
     * @param string $ref         reservation_id for traceability
     * @return array {ok, wallet_after}
     */
    public static function refund(int $userId, int $amountSdg, string $ref = ''): array {
        if ($amountSdg <= 0) return ['ok'=>true, 'wallet_after'=>self::getWalletBalance($userId)];

        try {
            db()->beginTransaction();

            // Credit back to wallet (not credit — wallet is the right place)
            db()->prepare("UPDATE wallet SET balance=balance+? WHERE user_id=?")
               ->execute([$amountSdg, $userId]);

            // Log transaction
            db()->prepare(
                "INSERT INTO wallet_transactions
                 (user_id, amount_sdg, type, reason, reservation_id, created_at)
                 VALUES (?, ?, 'credit', 'reservation_refund', ?, NOW())"
            )->execute([$userId, $amountSdg, $ref ?: null]);

            db()->commit();

            require_once __DIR__ . '/Logger.php';
            \Logger::info('wallet_refund', [
                'user_id'    => $userId,
                'amount_sdg' => $amountSdg,
                'ref'        => $ref,
            ]);

            return ['ok' => true, 'wallet_after' => self::getWalletBalance($userId)];

        } catch (\Throwable $e) {
            db()->rollBack();
            require_once __DIR__ . '/Logger.php';
            \Logger::error('wallet_refund_failed', ['error' => $e->getMessage()]);
            return ['ok' => false, 'error' => $e->getMessage()];
        }
    }

}