<?php

declare(strict_types=1);

require_once __DIR__ . '/rayan_common.php';

class RayanPanelService
{
    private array $config;

    public function __construct(?array $config = null)
    {
        $this->config = $config ?? require __DIR__ . '/../../config/rayan_config.php';
        $this->bootstrapStorage();
    }

    public function config(): array
    {
        return $this->config;
    }

    public function pdo(): PDO
    {
        $pdo = rayanDb($this->config);
        rayanEnsureSchema($pdo);
        rayanEnsureHesabanInvoiceSchema($pdo);
        return $pdo;
    }

    public function bootstrapStorage(): void
    {
        rayanEnsureDir((string)($this->config['paths']['runtime_dir'] ?? (__DIR__ . '/../../storage/rayan_runtime')));
        rayanEnsureDir((string)($this->config['paths']['logs_dir'] ?? (__DIR__ . '/../../storage/rayan_logs')));
    }


    private function manualFormSettingsPath(): string
    {
        $storage = app_path('storage');
        ensure_dir($storage);
        return rtrim($storage, '/\\') . '/rayan_manual_form_settings.json';
    }

    private function normalizeManualOptionName(string $name): string
    {
        return rayanNormalizeSpaces($name);
    }

    private function normalizeManualProductCode(string $code): string
    {
        $code = trim(rayanFaToEn($code));
        $code = preg_replace('/\s+/u', '', $code) ?? $code;
        $code = preg_replace('/\.0+$/', '', $code) ?? $code;
        return trim($code);
    }

    private function defaultManualFormSettings(): array
    {
        $planNames = [];
        foreach ((array)($this->config['club_routes'] ?? []) as $route) {
            if (!is_array($route)) {
                continue;
            }
            $name = $this->normalizeManualOptionName((string)($route['club'] ?? ''));
            if ($name !== '') {
                $planNames[] = $name;
            }
        }
        foreach (['بتا-آسان سطح 2', 'سایر طرح‌ها'] as $fallbackPlan) {
            $planNames[] = $fallbackPlan;
        }

        $plans = [];
        $seen = [];
        foreach ($planNames as $name) {
            $key = rayanNormalizeCompareText($name);
            if ($key === '' || isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $plans[] = [
                'name' => $name,
                'created_at' => date('Y-m-d H:i:s'),
            ];
        }

        return [
            'sales_plans' => $plans,
            'products' => [],
            'updated_at' => date('Y-m-d H:i:s'),
        ];
    }

    private function normalizeManualFormSettings(array $settings): array
    {
        $default = $this->defaultManualFormSettings();
        $rawPlans = $settings['sales_plans'] ?? [];
        if (!is_array($rawPlans) || !$rawPlans) {
            $rawPlans = $default['sales_plans'];
        }

        $plans = [];
        $seenPlans = [];
        foreach ($rawPlans as $plan) {
            $name = is_array($plan) ? (string)($plan['name'] ?? '') : (string)$plan;
            $name = $this->normalizeManualOptionName($name);
            $key = rayanNormalizeCompareText($name);
            if ($name === '' || $key === '' || isset($seenPlans[$key])) {
                continue;
            }
            $seenPlans[$key] = true;
            $plans[] = [
                'name' => $name,
                'created_at' => is_array($plan) ? (string)($plan['created_at'] ?? date('Y-m-d H:i:s')) : date('Y-m-d H:i:s'),
            ];
        }

        $rawProducts = $settings['products'] ?? [];
        if (!is_array($rawProducts)) {
            $rawProducts = [];
        }

        $products = [];
        foreach ($rawProducts as $product) {
            if (!is_array($product)) {
                continue;
            }
            $title = $this->normalizeManualOptionName((string)($product['title'] ?? $product['name'] ?? ''));
            $code = $this->normalizeManualProductCode((string)($product['code'] ?? $product['id'] ?? $product['product_code'] ?? ''));
            if ($title === '' || $code === '') {
                continue;
            }
            $products[$code] = [
                'title' => $title,
                'code' => $code,
                'created_at' => (string)($product['created_at'] ?? date('Y-m-d H:i:s')),
                'updated_at' => (string)($product['updated_at'] ?? ''),
            ];
        }

        uasort($products, static function (array $a, array $b): int {
            return strcmp((string)$a['title'], (string)$b['title']);
        });

        return [
            'sales_plans' => array_values($plans),
            'products' => array_values($products),
            'updated_at' => (string)($settings['updated_at'] ?? date('Y-m-d H:i:s')),
        ];
    }

    public function getManualFormSettings(): array
    {
        $file = $this->manualFormSettingsPath();
        if (!is_file($file)) {
            $settings = $this->defaultManualFormSettings();
            $this->saveManualFormSettings($settings);
            return $settings;
        }

        $raw = (string)file_get_contents($file);
        $data = json_decode($raw, true);
        if (!is_array($data)) {
            $data = $this->defaultManualFormSettings();
        }

        $settings = $this->normalizeManualFormSettings($data);
        if ($settings !== $data) {
            $this->saveManualFormSettings($settings);
        }
        return $settings;
    }

    private function saveManualFormSettings(array $settings): void
    {
        $settings = $this->normalizeManualFormSettings($settings);
        $settings['updated_at'] = date('Y-m-d H:i:s');
        file_put_contents(
            $this->manualFormSettingsPath(),
            json_encode($settings, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT),
            LOCK_EX
        );
    }

    private function parseManualProductLine(string $line): ?array
    {
        $line = trim($line);
        if ($line === '') {
            return null;
        }

        $parts = [];
        foreach (["\t", '|', ';', '؛'] as $delimiter) {
            if (str_contains($line, $delimiter)) {
                $parts = explode($delimiter, $line, 2);
                break;
            }
        }
        if (!$parts) {
            $pos = max(strrpos($line, ','), strrpos($line, '،'));
            if ($pos !== false && $pos > 0) {
                $parts = [substr($line, 0, $pos), substr($line, $pos + 1)];
            }
        }
        if (count($parts) !== 2) {
            return null;
        }

        $first = $this->normalizeManualOptionName((string)$parts[0]);
        $second = $this->normalizeManualOptionName((string)$parts[1]);
        if ($first === '' || $second === '') {
            return null;
        }

        $firstCode = $this->normalizeManualProductCode($first);
        $secondCode = $this->normalizeManualProductCode($second);
        $firstLooksCode = preg_match('/^[A-Za-z0-9_.\-]+$/', $firstCode) === 1 && preg_match('/\p{L}/u', $first) !== 1;
        $secondLooksCode = preg_match('/^[A-Za-z0-9_.\-]+$/', $secondCode) === 1;

        if ($firstLooksCode && !$secondLooksCode) {
            return ['title' => $second, 'code' => $firstCode];
        }
        return ['title' => $first, 'code' => $secondCode];
    }

    public function updateManualFormSettings(array $input): array
    {
        $settings = $this->getManualFormSettings();
        $action = (string)($input['settings_action'] ?? '');
        $message = 'تنظیمات فرم ذخیره شد.';

        if ($action === 'add_plan') {
            $name = $this->normalizeManualOptionName((string)($input['sales_plan_name'] ?? ''));
            if ($name === '') {
                throw new RuntimeException('نام طرح فروش را وارد کن.');
            }
            $settings['sales_plans'][] = ['name' => $name, 'created_at' => date('Y-m-d H:i:s')];
            $message = 'طرح فروش اضافه شد.';
        } elseif ($action === 'bulk_add_plans') {
            $lines = preg_split('/\R/u', (string)($input['sales_plan_bulk'] ?? '')) ?: [];
            $added = 0;
            foreach ($lines as $line) {
                $name = $this->normalizeManualOptionName((string)$line);
                if ($name === '') {
                    continue;
                }
                $settings['sales_plans'][] = ['name' => $name, 'created_at' => date('Y-m-d H:i:s')];
                $added++;
            }
            if ($added <= 0) {
                throw new RuntimeException('هیچ طرح معتبری برای افزودن پیدا نشد.');
            }
            $message = $added . ' طرح فروش اضافه/به‌روزرسانی شد.';
        } elseif ($action === 'delete_plans') {
            $selected = array_map('strval', (array)($input['selected_sales_plans'] ?? []));
            $remove = [];
            foreach ($selected as $name) {
                $key = rayanNormalizeCompareText($name);
                if ($key !== '') {
                    $remove[$key] = true;
                }
            }
            if (!$remove) {
                throw new RuntimeException('هیچ طرحی برای حذف انتخاب نشده است.');
            }
            $before = count((array)$settings['sales_plans']);
            $settings['sales_plans'] = array_values(array_filter((array)$settings['sales_plans'], function ($plan) use ($remove): bool {
                $name = is_array($plan) ? (string)($plan['name'] ?? '') : (string)$plan;
                return !isset($remove[rayanNormalizeCompareText($name)]);
            }));
            $message = ($before - count($settings['sales_plans'])) . ' طرح فروش حذف شد.';
        } elseif ($action === 'add_product') {
            $title = $this->normalizeManualOptionName((string)($input['product_title'] ?? ''));
            $code = $this->normalizeManualProductCode((string)($input['product_code_new'] ?? ''));
            if ($title === '' || $code === '') {
                throw new RuntimeException('نام کالا و عدد/ID حسابان‌وب هر دو اجباری هستند.');
            }
            $settings['products'][] = [
                'title' => $title,
                'code' => $code,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ];
            $message = 'کالا اضافه/به‌روزرسانی شد.';
        } elseif ($action === 'bulk_add_products') {
            $lines = preg_split('/\R/u', (string)($input['products_bulk'] ?? '')) ?: [];
            $added = 0;
            $invalid = 0;
            foreach ($lines as $line) {
                $parsed = $this->parseManualProductLine((string)$line);
                if (!$parsed) {
                    if (trim((string)$line) !== '') {
                        $invalid++;
                    }
                    continue;
                }
                $settings['products'][] = [
                    'title' => $parsed['title'],
                    'code' => $parsed['code'],
                    'created_at' => date('Y-m-d H:i:s'),
                    'updated_at' => date('Y-m-d H:i:s'),
                ];
                $added++;
            }
            if ($added <= 0) {
                throw new RuntimeException('هیچ کالای معتبری برای افزودن پیدا نشد. فرمت پیشنهادی هر خط: نام کالا | عدد حسابان‌وب');
            }
            $message = $added . ' کالا اضافه/به‌روزرسانی شد.' . ($invalid > 0 ? ' ردیف نامعتبر: ' . $invalid : '');
        } elseif ($action === 'delete_products') {
            $selected = array_map(fn($v) => $this->normalizeManualProductCode((string)$v), (array)($input['selected_product_codes'] ?? []));
            $remove = [];
            foreach ($selected as $code) {
                if ($code !== '') {
                    $remove[$code] = true;
                }
            }
            if (!$remove) {
                throw new RuntimeException('هیچ کالایی برای حذف انتخاب نشده است.');
            }
            $before = count((array)$settings['products']);
            $settings['products'] = array_values(array_filter((array)$settings['products'], function ($product) use ($remove): bool {
                $code = is_array($product) ? $this->normalizeManualProductCode((string)($product['code'] ?? '')) : '';
                return !isset($remove[$code]);
            }));
            $message = ($before - count($settings['products'])) . ' کالا حذف شد.';
        } else {
            throw new RuntimeException('عملیات تنظیمات فرم معتبر نیست.');
        }

        $this->saveManualFormSettings($settings);
        return [
            'message' => $message,
            'settings' => $this->getManualFormSettings(),
        ];
    }

    public function paginateManualProducts(array $settings, string $q = '', int $page = 1, int $perPage = 100): array
    {
        $products = array_values((array)($settings['products'] ?? []));
        $q = rayanNormalizeCompareText($q);
        if ($q !== '') {
            $products = array_values(array_filter($products, static function (array $product) use ($q): bool {
                $haystack = rayanNormalizeCompareText((string)($product['title'] ?? '') . ' ' . (string)($product['code'] ?? ''));
                return mb_stripos($haystack, $q, 0, 'UTF-8') !== false;
            }));
        }
        $page = max(1, $page);
        $perPage = in_array($perPage, [50, 100, 200, 500], true) ? $perPage : 100;
        $total = count($products);
        $lastPage = max(1, (int)ceil($total / $perPage));
        if ($page > $lastPage) {
            $page = $lastPage;
        }
        $offset = ($page - 1) * $perPage;
        return [
            'rows' => array_slice($products, $offset, $perPage),
            'pagination' => [
                'page' => $page,
                'per_page' => $perPage,
                'total' => $total,
                'last_page' => $lastPage,
                'from' => $total > 0 ? $offset + 1 : 0,
                'to' => min($offset + $perPage, $total),
            ],
        ];
    }

    private function findManualProductByCode(string $code): ?array
    {
        $code = $this->normalizeManualProductCode($code);
        if ($code === '') {
            return null;
        }
        foreach ((array)($this->getManualFormSettings()['products'] ?? []) as $product) {
            if (!is_array($product)) {
                continue;
            }
            if ($this->normalizeManualProductCode((string)($product['code'] ?? '')) === $code) {
                return $product;
            }
        }
        return null;
    }

    private function manualSalesPlanExists(string $name): bool
    {
        return $this->findManualSalesPlanName($name) !== null;
    }

    private function findManualSalesPlanName(string $name): ?string
    {
        $key = rayanNormalizeCompareText($name);
        if ($key === '') {
            return null;
        }
        foreach ((array)($this->getManualFormSettings()['sales_plans'] ?? []) as $plan) {
            $planName = is_array($plan) ? (string)($plan['name'] ?? '') : (string)$plan;
            if (rayanNormalizeCompareText($planName) === $key) {
                return rayanNormalizeSpaces($planName);
            }
        }
        return null;
    }

    private function findManualProductByTitleOrCode(string $title, string $code = ''): ?array
    {
        $titleKey = rayanNormalizeCompareText($title);
        $codeKey = $this->normalizeManualProductCode($code);
        foreach ((array)($this->getManualFormSettings()['products'] ?? []) as $product) {
            if (!is_array($product)) {
                continue;
            }
            $productTitle = rayanNormalizeCompareText((string)($product['title'] ?? ''));
            $productCode = $this->normalizeManualProductCode((string)($product['code'] ?? ''));
            if ($codeKey !== '' && $productCode !== '' && $productCode === $codeKey) {
                return $product;
            }
            if ($titleKey !== '' && $productTitle !== '' && $productTitle === $titleKey) {
                return $product;
            }
        }
        return null;
    }

    private function canonicalizeIncomingFormPayload(array $payload): array
    {
        if (!isset($payload['form']) || !is_array($payload['form'])) {
            return $payload;
        }
        $form = $payload['form'];
        $rawSource = rayanNormalizeSpaces((string)($payload['source'] ?? ''));
        $urlReferrer = rayanNormalizeSpaces((string)($form['urlReferrer'] ?? ''));
        if ($rawSource === 'manual_site_form' || $urlReferrer === 'manual-site-form') {
            return $payload;
        }

        $params = isset($form['params']) && is_array($form['params']) ? $form['params'] : [];
        $data = rayanBuildRequestDataFromForm($form, $params, $this->config);
        $clubLevel = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'club_level', ['نوع طرح فروش', 'طرح فروش', 'نوع طرح', 'کلوپ']));
        $canonicalClub = $this->findManualSalesPlanName($clubLevel) ?? rayanNormalizeSpaces($clubLevel);

        $productName = rayanNormalizeSpaces((string)($data['product_name'] ?? ''));
        $productCode = $this->normalizeManualProductCode((string)($data['product_code'] ?? ''));
        $mappedProduct = $this->findManualProductByTitleOrCode($productName, $productCode);
        if ($mappedProduct) {
            $productName = rayanNormalizeSpaces((string)($mappedProduct['title'] ?? $productName));
            $productCode = $this->normalizeManualProductCode((string)($mappedProduct['code'] ?? $productCode));
        }

        $sourceMode = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'source_mode', ['فروش از کالا ها', 'فروش از کالاها', 'روش تامین کالا', 'روش تأمین کالا', 'نوع فروش']));
        $sellerStore = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'seller_store_title', ['خرید از فروشگاه', 'نام فروشگاه', 'همکار']));
        $sellerName = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'seller_name', ['نام فروشنده']));

        $canonicalParams = [];
        $addParam = static function (int $fieldId, string $fieldName, string $value, ?string $title = null, ?string $number = null) use (&$canonicalParams): void {
            $value = trim($value);
            $title = $title === null ? $value : trim($title);
            if ($value === '' && $title === '' && ($number === null || trim($number) === '')) {
                return;
            }
            $canonicalParams[] = [
                'id' => count($canonicalParams) + 900000,
                'fieldId' => $fieldId,
                'fieldName' => $fieldName,
                'title' => $title,
                'value' => $value,
                'number' => ($number !== null && trim($number) !== '' && is_numeric($number)) ? $number : null,
                'group' => 'site_canonical_from_formafzar',
                'action' => null,
            ];
        };

        $addParam(1101, 'نام فروشنده', $sellerName);
        $addParam(1137, 'نام مشتری', (string)($data['buyer_name'] ?? ''));
        $addParam(1138, 'کد ملی', (string)($data['national_id'] ?? ''));
        $addParam(1139, 'موبایل / تلفن', (string)($data['mobile'] ?? ''));
        $addParam(1170, 'نوع طرح فروش', $canonicalClub);
        $addParam(1400, 'نام کالا', $productCode !== '' ? $productCode : $productName, $productName, is_numeric($productCode) ? $productCode : null);
        $addParam(1140, 'جمع کل باسود (بدون پیش پرداخت )', (string)($data['installment_amount'] ?? ''), (string)($data['installment_amount'] ?? ''), (string)($data['installment_amount'] ?? ''));
        $addParam(1395, 'انتقال به جامع کلی', (string)($data['sales_amount'] ?? ''), (string)($data['sales_amount'] ?? ''), (string)($data['sales_amount'] ?? ''));
        $addParam(1154, 'قیمت خرید حسابان وب', (string)($data['purchase_amount'] ?? ''), (string)($data['purchase_amount'] ?? ''), (string)($data['purchase_amount'] ?? ''));
        $addParam(1324, 'کد پستی', (string)($data['postal_code'] ?? ''));
        $addParam(1323, 'آدرس', (string)($data['address'] ?? ''));
        $addParam(1152, 'فروش از کالا ها', $sourceMode);
        $addParam(1399, 'خرید از فروشگاه', $sellerStore);

        $form['params'] = array_merge($canonicalParams, $params);
        $form['field_1101'] = $sellerName;
        $form['field_1137'] = (string)($data['buyer_name'] ?? '');
        $form['field_1138'] = (string)($data['national_id'] ?? '');
        $form['field_1139'] = (string)($data['mobile'] ?? '');
        $form['field_1170'] = $canonicalClub;
        $form['field_1400'] = $productCode !== '' ? $productCode : $productName;
        $form['field_1140'] = (string)($data['installment_amount'] ?? '');
        $form['field_1395'] = (string)($data['sales_amount'] ?? '');
        $form['field_1154'] = (string)($data['purchase_amount'] ?? '');
        $form['field_1324'] = (string)($data['postal_code'] ?? '');
        $form['field_1323'] = (string)($data['address'] ?? '');
        $form['field_1152'] = $sourceMode;
        $form['field_1399'] = $sellerStore;

        $payload['form'] = $form;
        return $payload;
    }

    private function currentPanelUser(): array
    {
        // وقتی سرویس از وبهوک عمومی/کران بدون نشست پنل اجرا می‌شود، نقش سیستم را مجاز در نظر می‌گیریم.
        if (!class_exists('Auth') || !Auth::check()) {
            return [
                'username' => '',
                'display_name' => '',
                'is_admin' => true,
            ];
        }

        $user = (array)Auth::user();
        $username = rayanNormalizeSpaces((string)($user['username'] ?? ''));
        $displayName = rayanNormalizeSpaces((string)($user['display_name'] ?? $username));
        return [
            'username' => $username,
            'display_name' => $displayName !== '' ? $displayName : $username,
            'is_admin' => Auth::isAdmin($user),
        ];
    }

    private function normalizeCreatorToken(string $value): string
    {
        $value = rayanFaToEn(rayanNormalizeSpaces($value));
        return mb_strtolower(trim($value), 'UTF-8');
    }

    private function panelUsersForMapping(): array
    {
        if (class_exists('Auth')) {
            try {
                return Auth::listUsers();
            } catch (Throwable $e) {
                // در endpoint عمومی وبهوک ممکن است Auth لود نشده باشد؛ از فایل کاربران می‌خوانیم.
            }
        }

        $storage = app_config()['storage_path'] ?? app_path('storage');
        $file = rtrim((string)$storage, '/\\') . '/users.json';
        if (!is_file($file)) {
            return [];
        }
        $data = json_decode((string)file_get_contents($file), true);
        if (!is_array($data)) {
            return [];
        }
        $rows = [];
        foreach ($data as $key => $row) {
            if (!is_array($row)) {
                continue;
            }
            $row['username'] = rayanNormalizeSpaces((string)($row['username'] ?? $key));
            $row['display_name'] = rayanNormalizeSpaces((string)($row['display_name'] ?? $row['username']));
            $rows[] = $row;
        }
        return $rows;
    }

    private function findPanelUserByFormafzarCreator(string $creatorId): ?array
    {
        $creatorId = rayanNormalizeSpaces($creatorId);
        if ($creatorId === '') {
            return null;
        }

        $needle = $this->normalizeCreatorToken($creatorId);
        foreach ($this->panelUsersForMapping() as $user) {
            if (!is_array($user) || (array_key_exists('is_active', $user) && empty($user['is_active']))) {
                continue;
            }
            $tokens = [
                (string)($user['username'] ?? ''),
                (string)($user['display_name'] ?? ''),
                (string)($user['formafzar_creator_id'] ?? ''),
            ];
            foreach ((array)($user['formafzar_creator_ids'] ?? []) as $extraToken) {
                $tokens[] = (string)$extraToken;
            }
            foreach ($tokens as $token) {
                if ($this->normalizeCreatorToken($token) !== '' && $this->normalizeCreatorToken($token) === $needle) {
                    return $user;
                }
            }
        }
        return null;
    }

    private function resolveRequestOwnership(array $payload, array $form): array
    {
        $rawSource = rayanNormalizeSpaces((string)($payload['source'] ?? ''));
        $urlReferrer = rayanNormalizeSpaces((string)($form['urlReferrer'] ?? ''));
        $isManual = $rawSource === 'manual_site_form' || $urlReferrer === 'manual-site-form';
        $source = $isManual ? 'manual_site_form' : 'webhook';

        $creatorId = rayanNormalizeSpaces((string)($payload['created_by'] ?? $form['creatorId'] ?? ''));
        $creatorDisplay = rayanNormalizeSpaces((string)($payload['created_by_display'] ?? ''));

        if ($isManual) {
            $actor = $this->currentPanelUser();
            if ($creatorId === '' && $actor['username'] !== '') {
                $creatorId = $actor['username'];
            }
            if ($creatorDisplay === '' && $actor['display_name'] !== '') {
                $creatorDisplay = $actor['display_name'];
            }
            if ($creatorDisplay === '') {
                $creatorDisplay = $creatorId;
            }
        } else {
            // وبهوک فرم‌افزار هم مالک پنلی می‌گیرد تا هر کاربر فقط فرم‌های خودش را ببیند.
            $mappedUser = $this->findPanelUserByFormafzarCreator($creatorId);
            if ($mappedUser) {
                $creatorId = rayanNormalizeSpaces((string)($mappedUser['username'] ?? ''));
                $creatorDisplay = rayanNormalizeSpaces((string)($mappedUser['display_name'] ?? $creatorId));
            } else {
                $creatorDisplay = $creatorDisplay !== '' ? $creatorDisplay : ($creatorId !== '' ? $creatorId : 'وبهوک / فرم‌افزار');
            }
        }

        return [
            'source' => $source,
            'created_by' => $creatorId,
            'created_by_display' => $creatorDisplay,
        ];
    }

    private function requestBelongsToCurrentUser(array $request): bool
    {
        $actor = $this->currentPanelUser();
        if ((bool)$actor['is_admin']) {
            return true;
        }
        $username = (string)$actor['username'];
        return $username !== ''
            && in_array((string)($request['source'] ?? ''), ['manual_site_form', 'webhook'], true)
            && (string)($request['created_by'] ?? '') === $username;
    }

    private function assertCurrentUserCanUseRequest(array $request): void
    {
        if (!$this->requestBelongsToCurrentUser($request)) {
            throw new RuntimeException('شما به این درخواست دسترسی ندارید. کاربران عادی فقط فرم‌های منتسب به خودشان را می‌بینند و مدیریت می‌کنند.');
        }
    }

    private function isRequestManagerApproved(array $request): bool
    {
        $submittedBy = trim((string)($request['submitted_by'] ?? ''));
        $submittedAt = trim((string)($request['submitted_at'] ?? ''));
        if ($submittedBy !== '' || $submittedAt !== '') {
            return true;
        }

        // ردیف‌های قدیمی ممکن است ثبت‌کننده نهایی نداشته باشند؛ هر وضعیتی غیر از آماده/ناقص یعنی وارد پردازش مدیریتی شده است.
        return !in_array((string)($request['status'] ?? ''), ['new', 'invalid', 'failed'], true);
    }

    private function assertCurrentUserIsAdminForFinalSubmission(): void
    {
        $actor = $this->currentPanelUser();
        if (empty($actor['is_admin'])) {
            throw new RuntimeException('ثبت نهایی، تلاش مجدد و ثبت کد تایید فقط برای ادمین مجاز است.');
        }
    }

    private function assertCurrentUserCanDeleteReports(): void
    {
        $actor = $this->currentPanelUser();
        if (empty($actor['is_admin'])) {
            throw new RuntimeException('حذف گروهی گزارش‌ها فقط برای ادمین مجاز است.');
        }
    }

    public function requestAmountFieldVisibility(): array
    {
        if (!class_exists('Auth') || !Auth::check() || Auth::isAdmin()) {
            return [
                'installment_amount' => true,
                'sales_amount' => true,
                'purchase_amount' => true,
            ];
        }

        return [
            'installment_amount' => !Auth::hasAssignedPermission('hide-request-installment-amount'),
            'sales_amount' => !Auth::hasAssignedPermission('hide-request-sales-amount'),
            'purchase_amount' => !Auth::hasAssignedPermission('hide-request-purchase-amount'),
        ];
    }

    private function stripHiddenRequestAmountInputs(array $input): array
    {
        $visibility = $this->requestAmountFieldVisibility();
        foreach ($visibility as $field => $isVisible) {
            if (!$isVisible && array_key_exists($field, $input)) {
                unset($input[$field]);
            }
        }
        return $input;
    }

    private function maskHiddenRequestAmountReportValues(array $values): array
    {
        $visibility = $this->requestAmountFieldVisibility();
        foreach ($visibility as $field => $isVisible) {
            if (!$isVisible) {
                $values[$field] = '';
            }
        }
        return $values;
    }

    public function canDeleteRequest(array $request): bool
    {
        $actor = $this->currentPanelUser();
        if (!empty($actor['is_admin'])) {
            return true;
        }

        $username = (string)($actor['username'] ?? '');
        return $username !== ''
            && in_array((string)($request['source'] ?? ''), ['manual_site_form', 'webhook'], true)
            && (string)($request['created_by'] ?? '') === $username
            && !$this->isRequestManagerApproved($request);
    }

    private function assertCurrentUserCanDeleteRequest(array $request): void
    {
        if (!$this->canDeleteRequest($request)) {
            throw new RuntimeException('حذف این درخواست مجاز نیست. کاربر عادی فقط فرم خودش را قبل از تایید/شروع عملیات مدیر می‌تواند حذف کند.');
        }
    }


    public function createManualRequest(array $input, bool $autoProcess = false): array
    {
        $input = $this->stripHiddenRequestAmountInputs($input);
        $payload = $this->buildManualFormPayload($input);
        $result = $this->handleIncomingFormPayload($payload, false);

        if ($autoProcess && !empty($result['request_id']) && (string)($result['status'] ?? '') === 'new') {
            $processResult = $this->processRequest((int)$result['request_id']);
            $request = $processResult['request'] ?? $this->getRequest((int)$result['request_id']);
            $result['status'] = (string)($request['status'] ?? $result['status']);
            $result['process_message'] = (string)($processResult['message'] ?? '');
        }

        return $result;
    }

    public function canEditManualRequest(array $request): bool
    {
        $status = (string)($request['status'] ?? '');
        $actor = $this->currentPanelUser();
        if (!empty($actor['is_admin'])) {
            return in_array($status, ['new', 'invalid', 'failed', 'external_done'], true);
        }

        return $this->requestBelongsToCurrentUser($request)
            && in_array($status, ['new', 'invalid', 'failed'], true)
            && !$this->isRequestManagerApproved($request);
    }

    private function rememberFinalSubmitter(PDO $pdo, int $requestId): void
    {
        $actor = $this->currentPanelUser();
        $username = rayanNormalizeSpaces((string)($actor['username'] ?? ''));
        $displayName = rayanNormalizeSpaces((string)($actor['display_name'] ?? ''));
        if ($username === '' && $displayName === '') {
            return;
        }
        if ($displayName === '') {
            $displayName = $username;
        }

        rayanUpdatePurchaseRequest($pdo, $requestId, [
            'submitted_by' => $username,
            'submitted_by_display' => $displayName,
            'submitted_at' => date('Y-m-d H:i:s'),
        ]);
    }

    public function updateManualRequest(int $requestId, array $input): array
    {
        $pdo = $this->pdo();
        $request = rayanGetPurchaseRequestById($pdo, $requestId);
        $this->assertCurrentUserCanUseRequest($request);

        if (!$this->canEditManualRequest($request)) {
            throw new RuntimeException('این فرم قابل ویرایش نیست. کاربر فقط فرم خودش را قبل از تایید/شروع عملیات مدیر می‌تواند ویرایش کند.');
        }

        $existingPayload = $this->getPayloadForRequest($request);
        $input = $this->stripHiddenRequestAmountInputs($input);
        $payload = $this->buildManualFormPayload($input, $existingPayload, $request);
        $result = $this->handleIncomingFormPayload($payload, false);

        return [
            'message' => 'درخواست #' . $requestId . ' ویرایش شد و آماده ثبت است.',
            'request' => $this->getRequest($requestId),
            'result' => $result,
        ];
    }

    private function getPayloadForRequest(array $request): ?array
    {
        $submissionId = (int)($request['submission_id'] ?? 0);
        if ($submissionId <= 0) {
            return null;
        }

        $stmt = $this->pdo()->prepare('SELECT raw_json FROM forms_submissions WHERE id = :id LIMIT 1');
        $stmt->execute([':id' => $submissionId]);
        $raw = (string)($stmt->fetchColumn() ?: '');
        if ($raw === '') {
            return null;
        }

        $payload = json_decode($raw, true);
        return is_array($payload) ? $payload : null;
    }

    public function getManualRequestFormValues(array $request): array
    {
        $values = [
            'club_level' => (string)($request['club_level'] ?? ''),
            'seller_name' => (string)($request['seller_name'] ?? ''),
            'buyer_name' => (string)($request['buyer_name'] ?? ''),
            'national_id' => (string)($request['national_id'] ?? ''),
            'mobile' => (string)($request['mobile'] ?? ''),
            'product_code' => '',
            'product_name' => (string)($request['product_name'] ?? ''),
            'installment_amount' => (string)($request['installment_amount'] ?? ''),
            'sales_amount' => '',
            'purchase_amount' => '',
            'postal_code' => (string)($request['postal_code'] ?? ''),
            'address' => (string)($request['address'] ?? ''),
            'source_mode' => '',
            'seller_store_title' => '',
        ];

        $payload = $this->getPayloadForRequest($request);
        $form = is_array($payload['form'] ?? null) ? $payload['form'] : [];
        $params = isset($form['params']) && is_array($form['params']) ? $form['params'] : [];
        if ($form && $params) {
            $data = rayanBuildRequestDataFromForm($form, $params, $this->config);
            foreach (['buyer_name', 'national_id', 'mobile', 'product_name', 'product_code', 'installment_amount', 'sales_amount', 'purchase_amount', 'postal_code', 'address'] as $key) {
                if (array_key_exists($key, $data) && trim((string)$data[$key]) !== '') {
                    $values[$key] = (string)$data[$key];
                }
            }

            $sellerName = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'seller_name', ['نام فروشنده']));
            if ($sellerName !== '') {
                $values['seller_name'] = $sellerName;
            }
            $clubLevel = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'club_level', ['نوع طرح فروش', 'طرح فروش', 'نوع طرح', 'کلوپ']));
            if ($clubLevel !== '') {
                $values['club_level'] = $clubLevel;
            }
            $sourceMode = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'source_mode', ['فروش از کالا ها', 'روش تامین کالا', 'روش تأمین کالا']));
            if ($sourceMode !== '') {
                $values['source_mode'] = $sourceMode;
            }
            $sellerStore = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'seller_store_title', ['خرید از فروشگاه', 'نام فروشگاه', 'همکار']));
            if ($sellerStore !== '') {
                $values['seller_store_title'] = $sellerStore;
            }
        }

        if ($values['product_code'] === '' && $values['product_name'] !== '') {
            foreach ((array)($this->getManualFormSettings()['products'] ?? []) as $product) {
                if (!is_array($product)) {
                    continue;
                }
                if (rayanNormalizeCompareText((string)($product['title'] ?? '')) === rayanNormalizeCompareText($values['product_name'])) {
                    $values['product_code'] = $this->normalizeManualProductCode((string)($product['code'] ?? ''));
                    break;
                }
            }
        }

        return $values;
    }

    private function buildManualFormPayload(array $input, ?array $existingPayload = null, ?array $existingRequest = null): array
    {
        $existingForm = is_array($existingPayload['form'] ?? null) ? $existingPayload['form'] : [];
        $existingParams = isset($existingForm['params']) && is_array($existingForm['params']) ? $existingForm['params'] : [];
        $existingData = ($existingForm && $existingParams) ? rayanBuildRequestDataFromForm($existingForm, $existingParams, $this->config) : [];

        $cleanText = static fn(string $key): string => rayanNormalizeSpaces((string)($input[$key] ?? ''));
        $cleanMoney = static function (string $key) use ($input, $existingData, $existingRequest): string {
            if (array_key_exists($key, $input)) {
                return rayanDigitsOnly((string)($input[$key] ?? ''));
            }
            if (array_key_exists($key, $existingData)) {
                return rayanDigitsOnly((string)$existingData[$key]);
            }
            if (is_array($existingRequest) && array_key_exists($key, $existingRequest)) {
                return rayanDigitsOnly((string)$existingRequest[$key]);
            }
            return '';
        };

        $sellerName = $cleanText('seller_name');
        $buyerName = $cleanText('buyer_name');
        $clubLevel = $cleanText('club_level');
        $nationalId = rayanDigitsOnly((string)($input['national_id'] ?? ''));
        $mobile = rayanDigitsOnly((string)($input['mobile'] ?? ''));
        if (strlen($mobile) === 10 && str_starts_with($mobile, '9')) {
            $mobile = '0' . $mobile;
        }
        $productCode = $this->normalizeManualProductCode((string)($input['product_code'] ?? ''));
        $product = $this->findManualProductByCode($productCode);
        $productName = $product ? $this->normalizeManualOptionName((string)($product['title'] ?? '')) : $cleanText('product_name');
        $installmentAmount = $cleanMoney('installment_amount');
        $salesAmount = $cleanMoney('sales_amount');
        $purchaseAmount = $cleanMoney('purchase_amount');
        $postalCode = rayanDigitsOnly((string)($input['postal_code'] ?? ''));
        $address = $cleanText('address');
        $sourceMode = $cleanText('source_mode');
        $sellerStoreTitle = $cleanText('seller_store_title');

        if ($clubLevel === '') {
            throw new RuntimeException('نوع طرح فروش را انتخاب کن.');
        }
        if (!$this->manualSalesPlanExists($clubLevel)) {
            throw new RuntimeException('طرح فروش انتخاب‌شده در تنظیمات فرم تعریف نشده است.');
        }
        if ($productCode === '' || !$product) {
            throw new RuntimeException('نام کالا را از لیست کالاهای تعریف‌شده در تنظیمات فرم انتخاب کن.');
        }

        $formEntryId = (int)($existingForm['id'] ?? $existingRequest['form_entry_id'] ?? 0);
        if ($formEntryId <= 0) {
            $formEntryId = $this->generateManualFormEntryId();
        }
        $now = date('Y-m-d H:i:s');
        $createdAt = rayanNormalizeSpaces((string)($existingPayload['created_at'] ?? $existingForm['createDate'] ?? ''));
        if ($createdAt === '') {
            $createdAt = $now;
        }

        $params = [];
        $addParam = static function (int $fieldId, string $fieldName, string $value, ?string $title = null, ?string $number = null) use (&$params): void {
            $value = trim($value);
            $title = $title === null ? $value : trim($title);
            if ($value === '' && $title === '' && ($number === null || trim($number) === '')) {
                return;
            }
            $params[] = [
                'id' => count($params) + 1,
                'fieldId' => $fieldId,
                'fieldName' => $fieldName,
                'title' => $title,
                'value' => $value,
                'number' => ($number !== null && trim($number) !== '' && is_numeric($number)) ? $number : null,
                'group' => 'manual_site_form',
                'action' => null,
            ];
        };

        $addParam(1101, 'نام فروشنده', $sellerName);
        $addParam(1137, 'نام مشتری', $buyerName);
        $addParam(1138, 'کد ملی', $nationalId);
        $addParam(1139, 'موبایل / تلفن', $mobile);
        $addParam(1170, 'نوع طرح فروش', $clubLevel);
        $addParam(1400, 'نام کالا', $productCode, $productName, is_numeric($productCode) ? $productCode : null);
        $addParam(1140, 'جمع کل باسود (بدون پیش پرداخت )', $installmentAmount, $installmentAmount, $installmentAmount);
        $addParam(1395, 'انتقال به جامع کلی', $salesAmount, $salesAmount, $salesAmount);
        $addParam(1154, 'قیمت خرید', $purchaseAmount, $purchaseAmount, $purchaseAmount);
        $addParam(1324, 'کد پستی', $postalCode);
        $addParam(1323, 'آدرس', $address);
        $addParam(1152, 'فروش از کالا ها', $sourceMode);
        $addParam(1399, 'خرید از فروشگاه', $sellerStoreTitle);
        $addParam(1501, 'تایید شد', 'تایید شد', 'تایید شد');
        $addParam(1502, 'فاکتور و بررسی نهایی', 'تایید شد', 'تایید شد');

        $actor = $this->currentPanelUser();
        $creatorUsername = rayanNormalizeSpaces((string)($existingPayload['created_by'] ?? $existingRequest['created_by'] ?? $existingForm['creatorId'] ?? ''));
        if ($creatorUsername === '') {
            $creatorUsername = $actor['username'] !== '' ? $actor['username'] : 'manual';
        }
        $creatorDisplay = rayanNormalizeSpaces((string)($existingPayload['created_by_display'] ?? $existingRequest['created_by_display'] ?? ''));
        if ($creatorDisplay === '') {
            $creatorDisplay = $actor['display_name'] !== '' ? $actor['display_name'] : $creatorUsername;
        }

        $payloadSource = rayanNormalizeSpaces((string)($existingPayload['source'] ?? $existingRequest['source'] ?? 'manual_site_form'));
        if (!in_array($payloadSource, ['manual_site_form', 'webhook'], true)) {
            $payloadSource = 'manual_site_form';
        }
        $urlReferrer = rayanNormalizeSpaces((string)($existingForm['urlReferrer'] ?? ''));
        if ($payloadSource === 'manual_site_form' || $urlReferrer === '') {
            $urlReferrer = 'manual-site-form';
        }

        $form = [
            'id' => $formEntryId,
            'formattedId' => rayanNormalizeSpaces((string)($existingForm['formattedId'] ?? '')) !== '' ? (string)$existingForm['formattedId'] : 'MAN-' . $formEntryId,
            'creatorId' => $creatorUsername,
            'createDate' => $createdAt,
            'urlReferrer' => $urlReferrer,
            'browser' => rayanNormalizeSpaces((string)($existingForm['browser'] ?? '')) !== '' ? (string)$existingForm['browser'] : ($_SERVER['HTTP_USER_AGENT'] ?? 'manual-site-form'),
            'ip' => rayanNormalizeSpaces((string)($existingForm['ip'] ?? '')) !== '' ? (string)$existingForm['ip'] : ($_SERVER['REMOTE_ADDR'] ?? ''),
            'paymentStatus' => null,
            'params' => $params,
            'field_1101' => $sellerName,
            'field_1137' => $buyerName,
            'field_1138' => $nationalId,
            'field_1139' => $mobile,
            'field_1170' => $clubLevel,
            'field_1400' => $productCode !== '' ? $productCode : $productName,
            'field_1140' => $installmentAmount,
            'field_1395' => $salesAmount,
            'field_1154' => $purchaseAmount,
            'field_1324' => $postalCode,
            'field_1323' => $address,
            'field_1152' => $sourceMode,
            'field_1399' => $sellerStoreTitle,
            'field_1501' => 'تایید شد',
            'field_1502' => 'تایید شد',
        ];

        return [
            'source' => $payloadSource,
            'created_by' => $creatorUsername,
            'created_by_display' => $creatorDisplay,
            'created_at' => $createdAt,
            'form' => $form,
        ];
    }

    private function generateManualFormEntryId(): int
    {
        $pdo = $this->pdo();
        do {
            $id = (int)('9' . date('ymdHis') . random_int(10, 99));
            $stmt = $pdo->prepare('SELECT 1 FROM forms_submissions WHERE form_entry_id = :id LIMIT 1');
            $stmt->execute([':id' => $id]);
            $exists = (bool)$stmt->fetchColumn();
        } while ($exists);

        return $id;
    }

    public function handleIncomingFormPayload(array $payload, bool $autoProcess = false): array
    {
        if (!isset($payload['form']) || !is_array($payload['form'])) {
            throw new RuntimeException('form object is required.');
        }
        $payload = $this->canonicalizeIncomingFormPayload($payload);

        $form = $payload['form'];
        $params = isset($form['params']) && is_array($form['params']) ? $form['params'] : [];
        $formEntryId = isset($form['id']) ? (int)$form['id'] : 0;
        if ($formEntryId <= 0) {
            throw new RuntimeException('form.id معتبر نیست.');
        }

        $sellerName  = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'seller_name', ['نام فروشنده']));
        $finalStatus = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'approved_flag', ['تایید شد', 'تایید نهایی', 'وضعیت تایید', 'تایید شود']));
        $clubLevel   = rayanGetFieldValue($form, $params, [], rayanGetFieldNameList($this->config, 'club_level', ['نوع طرح فروش', 'طرح فروش', 'نوع طرح', 'کلوپ']));
        $requestData = rayanBuildRequestDataFromForm($form, $params, $this->config);
        $provider    = rayanResolveRequestProvider($this->config, $clubLevel);
        $intake      = rayanEvaluateFormIntakeRules($this->config, $form, $params, $clubLevel);
        $ownership   = $this->resolveRequestOwnership($payload, $form);

        $pdo = $this->pdo();
        $pdo->beginTransaction();
        try {
            $submission = rayanUpsertFormSubmission(
                $pdo,
                $this->config,
                $payload,
                $form,
                $params,
                $sellerName,
                $finalStatus,
                $clubLevel
            );

            if (!(bool)($intake['accepted'] ?? false)) {
                $pdo->commit();
                rayanWebhookLog($this->config, 'فرم دریافت شد ولی وارد گزارش درخواست‌ها نشد', [
                    'form_id' => $formEntryId,
                    'club_level' => $clubLevel,
                    'provider' => $provider,
                    'reason' => (string)($intake['reason'] ?? ''),
                ]);
                return [
                    'success' => true,
                    'saved' => true,
                    'request_created' => false,
                    'request_id' => 0,
                    'submission_id' => (int)($submission['submission_id'] ?? 0),
                    'status' => 'ignored',
                    'club_level' => $clubLevel,
                    'provider' => $provider,
                    'message' => (string)($intake['reason'] ?? 'فرم شرایط ورود به گزارش را نداشت.'),
                ];
            }

            $requestRow = rayanCreateOrUpdatePurchaseRequest($pdo, [
                'form_entry_id'       => $formEntryId,
                'submission_id'       => $submission['submission_id'],
                'seller_name'         => $sellerName,
                'buyer_name'          => (string)($requestData['buyer_name'] ?? ''),
                'final_status'        => $finalStatus,
                'club_level'          => $clubLevel,
                'national_id'         => (string)($requestData['national_id'] ?? ''),
                'mobile'              => (string)($requestData['mobile'] ?? ''),
                'product_name'        => (string)($requestData['product_name'] ?? ''),
                'installment_amount'  => trim((string)($requestData['installment_amount'] ?? '')) !== '' ? (string)$requestData['installment_amount'] : (string)($requestData['sales_amount'] ?? ''),
                'postal_code'         => (string)($requestData['postal_code'] ?? ''),
                'address'             => (string)($requestData['address'] ?? ''),
            ]);
            $preservedState = (bool)($requestRow['preserved_state'] ?? false);
            rayanUpdatePurchaseRequest($pdo, (int)$requestRow['id'], [
                'source' => (string)$ownership['source'],
                'created_by' => (string)$ownership['created_by'],
                'created_by_display' => (string)$ownership['created_by_display'],
            ]);
            $requestRow = rayanGetPurchaseRequestById($pdo, (int)$requestRow['id']);

            if (!$preservedState) {
                $status = 'new';
                $lastStep = $provider === 'hesaban_only'
                    ? 'آماده ثبت فاکتور فروش و خرید حسابان‌وب'
                    : 'آماده ثبت از داخل پنل';
                $error = null;

                $validation = $this->validateRequestForProvider($provider, $requestData);
                if ($validation !== '') {
                    $status = 'invalid';
                    $lastStep = 'اطلاعات فرم ناقص یا نامعتبر است';
                    $error = $validation;
                }

                rayanUpdatePurchaseRequest($pdo, (int)$requestRow['id'], [
                    'status' => $status,
                    'last_step' => $lastStep,
                    'error_message' => $error,
                ]);
            }

            $requestRow = rayanGetPurchaseRequestById($pdo, (int)$requestRow['id']);
            $pdo->commit();
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }

        if ($autoProcess && $requestRow['status'] === 'new') {
            try {
                $this->processRequest((int)$requestRow['id']);
                $requestRow = $this->getRequest((int)$requestRow['id']);
            } catch (Throwable $e) {
                rayanWebhookLog($this->config, 'خطا در پردازش خودکار فرم', [
                    'request_id' => (int)$requestRow['id'],
                    'error' => $e->getMessage(),
                ]);
            }
        }

        rayanWebhookLog($this->config, 'فرم در پنل وب ذخیره شد', [
            'form_id' => $formEntryId,
            'request_id' => (int)$requestRow['id'],
            'club_level' => $clubLevel,
            'provider' => $provider,
            'status' => $requestRow['status'],
            'source' => (string)($requestRow['source'] ?? ''),
            'created_by' => (string)($requestRow['created_by'] ?? ''),
        ]);

        return [
            'success' => true,
            'saved' => true,
            'request_created' => true,
            'request_id' => (int)$requestRow['id'],
            'submission_id' => (int)($requestRow['submission_id'] ?? 0),
            'status' => (string)$requestRow['status'],
            'club_level' => $clubLevel,
            'provider' => $provider,
            'source' => (string)($requestRow['source'] ?? ''),
            'created_by' => (string)($requestRow['created_by'] ?? ''),
        ];
    }

    private function validateRequestForProvider(string $provider, array $requestData): string
    {
        $nationalId = trim((string)($requestData['national_id'] ?? ''));
        $mobile = trim((string)($requestData['mobile'] ?? ''));
        $productName = trim((string)($requestData['product_name'] ?? ''));
        $amount = trim((string)($requestData['installment_amount'] ?? ''));
        $postal = trim((string)($requestData['postal_code'] ?? ''));
        $address = trim((string)($requestData['address'] ?? ''));

        if ($provider === 'rayan') {
            if ($nationalId === '' || $mobile === '' || $productName === '' || $postal === '' || $address === '') {
                return 'برای این طرح دومرحله‌ای این فیلدها اجباری است: کد ملی، موبایل، نام کالا، کد پستی، آدرس';
            }
            if (strlen($nationalId) !== 10) {
                return 'کد ملی باید ۱۰ رقم باشد.';
            }
            if (strlen($mobile) !== 11 || !str_starts_with($mobile, '09')) {
                return 'شماره موبایل باید ۱۱ رقم و با 09 شروع شود.';
            }
            if (strlen($postal) !== 10) {
                return 'کد پستی باید ۱۰ رقم باشد.';
            }
            return '';
        }

        if ($provider === 'minipay_direct') {
            if ($mobile === '') {
                return 'برای کلوپ سطح ۲/قسطانو شماره موبایل اجباری است.';
            }
            if (strlen($mobile) !== 11 || !str_starts_with($mobile, '09')) {
                return 'شماره موبایل باید ۱۱ رقم و با 09 شروع شود.';
            }
            return '';
        }

        if ($provider === 'hesaban_only') {
            $buyerName = trim((string)($requestData['buyer_name'] ?? ''));
            $productCode = trim((string)($requestData['product_code'] ?? ''));
            if ($buyerName === '') {
                return 'برای طرح‌های غیرکلوپی، نام خریدار/مشتری اجباری است.';
            }
            if ($nationalId === '') {
                return 'برای طرح‌های غیرکلوپی، کد ملی اجباری است.';
            }
            if ($productCode === '') {
                return 'برای طرح‌های غیرکلوپی، کد کالا حسابان/نام کالا باید قابل تشخیص باشد.';
            }
            return '';
        }

        return 'provider ناشناخته است: ' . $provider;
    }

    public function getRequest(int $requestId): array
    {
        return rayanGetPurchaseRequestById($this->pdo(), $requestId);
    }

    private function reportActorLabel(string $username, string $displayName, string $fallback = '-'): string
    {
        $username = rayanNormalizeSpaces($username);
        $displayName = rayanNormalizeSpaces($displayName);
        if ($displayName === 'مدیر اصلی') {
            return 'مدیر اصلی';
        }
        if ($username !== '') {
            return $username;
        }
        if ($displayName !== '') {
            return $displayName;
        }
        return $fallback;
    }

    private function gregorianToJalali(int $gy, int $gm, int $gd): array
    {
        $gdm = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
        if ($gy > 1600) {
            $jy = 979;
            $gy -= 1600;
        } else {
            $jy = 0;
            $gy -= 621;
        }

        $gy2 = $gm > 2 ? $gy + 1 : $gy;
        $days = 365 * $gy
            + intdiv($gy2 + 3, 4)
            - intdiv($gy2 + 99, 100)
            + intdiv($gy2 + 399, 400)
            - 80
            + $gd
            + $gdm[$gm - 1];

        $jy += 33 * intdiv($days, 12053);
        $days %= 12053;
        $jy += 4 * intdiv($days, 1461);
        $days %= 1461;

        if ($days > 365) {
            $jy += intdiv($days - 1, 365);
            $days = ($days - 1) % 365;
        }

        if ($days < 186) {
            $jm = 1 + intdiv($days, 31);
            $jd = 1 + ($days % 31);
        } else {
            $jm = 7 + intdiv($days - 186, 30);
            $jd = 1 + (($days - 186) % 30);
        }

        return [$jy, $jm, $jd];
    }

    private function jalaliToGregorian(int $jy, int $jm, int $jd): array
    {
        $jy += 1595;
        $days = -355668 + (365 * $jy) + (8 * intdiv($jy, 33)) + intdiv((($jy % 33) + 3), 4) + $jd;
        $days += ($jm < 7) ? (($jm - 1) * 31) : ((($jm - 7) * 30) + 186);

        $gy = 400 * intdiv($days, 146097);
        $days %= 146097;

        if ($days > 36524) {
            $gy += 100 * intdiv(--$days, 36524);
            $days %= 36524;
            if ($days >= 365) {
                $days++;
            }
        }

        $gy += 4 * intdiv($days, 1461);
        $days %= 1461;

        if ($days > 365) {
            $gy += intdiv($days - 1, 365);
            $days = ($days - 1) % 365;
        }

        $gd = $days + 1;
        $leap = (($gy % 4 === 0) && ($gy % 100 !== 0)) || ($gy % 400 === 0);
        $monthDays = [0, 31, $leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        for ($gm = 1; $gm <= 12 && $gd > $monthDays[$gm]; $gm++) {
            $gd -= $monthDays[$gm];
        }

        return [$gy, $gm, $gd];
    }

    public function formatJalaliDateTime(?string $value): string
    {
        $value = trim((string)$value);
        if ($value === '' || $value === '0000-00-00 00:00:00') {
            return '-';
        }

        $timestamp = strtotime($value);
        if ($timestamp === false) {
            return $value;
        }

        [$jy, $jm, $jd] = $this->gregorianToJalali(
            (int)date('Y', $timestamp),
            (int)date('m', $timestamp),
            (int)date('d', $timestamp)
        );

        return sprintf('%04d/%02d/%02d %s', $jy, $jm, $jd, date('H:i', $timestamp));
    }

    private function parseJalaliDateTimeFilter(string $value, bool $endOfDay): ?int
    {
        $value = trim(rayanFaToEn($value));
        if ($value === '') {
            return null;
        }

        if (!preg_match('/^(\d{4})[\/-](\d{1,2})[\/-](\d{1,2})(?:\s+(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?$/', $value, $m)) {
            return null;
        }

        $year = (int)$m[1];
        $month = (int)$m[2];
        $day = (int)$m[3];
        $hour = isset($m[4]) && $m[4] !== '' ? (int)$m[4] : ($endOfDay ? 23 : 0);
        $minute = isset($m[5]) && $m[5] !== '' ? (int)$m[5] : ($endOfDay ? 59 : 0);
        $second = isset($m[6]) && $m[6] !== '' ? (int)$m[6] : ($endOfDay ? 59 : 0);

        if ($year < 1700) {
            [$gy, $gm, $gd] = $this->jalaliToGregorian($year, $month, $day);
        } else {
            [$gy, $gm, $gd] = [$year, $month, $day];
        }

        $timestamp = mktime($hour, $minute, $second, $gm, $gd, $gy);
        return $timestamp !== false ? $timestamp : null;
    }

    private function reportFilterNormalize(string $value): string
    {
        $value = rayanFaToEn($value);
        $value = rayanNormalizeCompareText($value);
        return trim($value);
    }

    private function reportFilterContains(string $haystack, string $needle): bool
    {
        $haystack = $this->reportFilterNormalize($haystack);
        $needle = $this->reportFilterNormalize($needle);
        if ($needle === '') {
            return true;
        }
        if (mb_stripos($haystack, $needle, 0, 'UTF-8') !== false) {
            return true;
        }

        $plainHaystack = preg_replace('/[\s,،\/\-_:]+/u', '', $haystack) ?? $haystack;
        $plainNeedle = preg_replace('/[\s,،\/\-_:]+/u', '', $needle) ?? $needle;
        return $plainNeedle !== '' && mb_stripos($plainHaystack, $plainNeedle, 0, 'UTF-8') !== false;
    }

    public function requestReportColumns(): array
    {
        return [
            'id' => '#',
            'created_at' => 'تاریخ ایجاد',
            'owner' => 'ایجادکننده',
            'submitter' => 'ثبت نهایی توسط',
            'submitted_at' => 'زمان ثبت نهایی',
            'plan' => 'طرح فروش',
            'final' => 'تایید',
            'status' => 'وضعیت',
            'customer' => 'نام مشتری',
            'national' => 'کد ملی',
            'mobile' => 'موبایل',
            'product' => 'نام کالا',
            'installment_amount' => 'مبلغ خرید اعتباری / با سود',
            'sales_amount' => 'مبلغ فروش حسابان‌وب',
            'purchase_amount' => 'مبلغ خرید حسابان‌وب',
            'postal' => 'کد پستی',
            'source_mode' => 'روش تأمین کالا',
            'store' => 'خرید از فروشگاه / همکار',
            'address' => 'آدرس',
            'step' => 'مرحله',
            'error' => 'خطا',
            'tracking' => 'کد پیگیری',
        ];
    }

    private function reportColumnKeys(): array
    {
        return array_keys($this->requestReportColumns());
    }

    private function buildReportFilterValues(array $row, array $formValues): array
    {
        $ownerLabel = $this->reportActorLabel(
            (string)($row['created_by'] ?? ''),
            (string)($row['created_by_display'] ?? ''),
            'وبهوک / فرم‌افزار'
        );

        $submitterLabel = $this->reportActorLabel(
            (string)($row['submitted_by'] ?? ''),
            (string)($row['submitted_by_display'] ?? ''),
            '-'
        );

        $buyerName = trim((string)($formValues['buyer_name'] ?? $row['buyer_name'] ?? ''));
        $productDisplay = trim((string)($formValues['product_name'] ?? $row['product_name'] ?? ''));
        $installmentDisplay = trim((string)($formValues['installment_amount'] ?? $row['installment_amount'] ?? ''));
        $salesDisplay = trim((string)($formValues['sales_amount'] ?? ''));
        $purchaseDisplay = trim((string)($formValues['purchase_amount'] ?? ''));
        $postalDisplay = trim((string)($formValues['postal_code'] ?? $row['postal_code'] ?? ''));
        $sourceModeDisplay = trim((string)($formValues['source_mode'] ?? ''));
        $sellerStoreDisplay = trim((string)($formValues['seller_store_title'] ?? ''));
        $addressDisplay = trim((string)($formValues['address'] ?? $row['address'] ?? ''));
        $createdAt = trim((string)($row['created_at'] ?? ''));
        $submittedAt = trim((string)($row['submitted_at'] ?? ''));

        $moneyText = static function (string $amount): string {
            if (trim($amount) === '') {
                return '';
            }
            return $amount . ' ' . rayanFormatMoney($amount);
        };

        return [
            'id' => (string)($row['id'] ?? ''),
            'created_at' => $createdAt . ' ' . $this->formatJalaliDateTime($createdAt),
            'owner' => $ownerLabel,
            'submitter' => $submitterLabel,
            'submitted_at' => $submittedAt . ' ' . $this->formatJalaliDateTime($submittedAt),
            'plan' => (string)($row['club_level'] ?? ''),
            'final' => (string)($row['final_status'] ?? ''),
            'status' => $this->statusLabel((string)($row['status'] ?? '')) . ' ' . (string)($row['status'] ?? ''),
            'customer' => $buyerName,
            'national' => (string)($row['national_id'] ?? ''),
            'mobile' => (string)($row['mobile'] ?? ''),
            'product' => $productDisplay,
            'installment_amount' => $moneyText($installmentDisplay),
            'sales_amount' => $moneyText($salesDisplay),
            'purchase_amount' => $moneyText($purchaseDisplay),
            'postal' => $postalDisplay,
            'source_mode' => $sourceModeDisplay,
            'store' => $sellerStoreDisplay,
            'address' => $addressDisplay,
            'step' => (string)($row['last_step'] ?? ''),
            'error' => (string)($row['error_message'] ?? ''),
            'tracking' => (string)($row['minipay_tracking_code'] ?? ''),
        ];
    }

    private function rowMatchesReportFilters(array $row, array $values, array $filters): bool
    {
        $createdFrom = $this->parseJalaliDateTimeFilter((string)($filters['created_from'] ?? ''), false);
        $createdTo = $this->parseJalaliDateTimeFilter((string)($filters['created_to'] ?? ''), true);
        $submittedFrom = $this->parseJalaliDateTimeFilter((string)($filters['submitted_from'] ?? ''), false);
        $submittedTo = $this->parseJalaliDateTimeFilter((string)($filters['submitted_to'] ?? ''), true);

        $createdTs = strtotime((string)($row['created_at'] ?? ''));
        if ($createdFrom !== null && ($createdTs === false || $createdTs < $createdFrom)) {
            return false;
        }
        if ($createdTo !== null && ($createdTs === false || $createdTs > $createdTo)) {
            return false;
        }

        $submittedAt = trim((string)($row['submitted_at'] ?? ''));
        $submittedTs = $submittedAt !== '' ? strtotime($submittedAt) : false;
        if ($submittedFrom !== null && ($submittedTs === false || $submittedTs < $submittedFrom)) {
            return false;
        }
        if ($submittedTo !== null && ($submittedTs === false || $submittedTs > $submittedTo)) {
            return false;
        }

        $q = trim((string)($filters['q'] ?? ''));
        if ($q !== '' && !$this->reportFilterContains(implode(' ', $values), $q)) {
            return false;
        }

        $columnFilters = is_array($filters['col'] ?? null) ? (array)$filters['col'] : [];
        foreach ($this->reportColumnKeys() as $key) {
            $needle = trim((string)($columnFilters[$key] ?? ''));
            if ($needle === '') {
                continue;
            }
            if (!$this->reportFilterContains((string)($values[$key] ?? ''), $needle)) {
                return false;
            }
        }

        return true;
    }

    private function collectFilteredRequestsForReport(array $filters = []): array
    {
        $pdo = $this->pdo();
        $where = [];
        $params = [];

        $actor = $this->currentPanelUser();
        $isAdmin = (bool)$actor['is_admin'];
        $actorUsername = (string)$actor['username'];

        if (!$isAdmin) {
            if ($actorUsername === '') {
                $where[] = '1 = 0';
            } else {
                $where[] = 'created_by = :scope_created_by';
                $params[':scope_created_by'] = $actorUsername;
            }
        } else {
            $createdBy = trim((string)($filters['created_by'] ?? ''));
            if ($createdBy !== '') {
                if ($createdBy === '__webhook__') {
                    $where[] = "COALESCE(NULLIF(created_by, ''), '') = ''";
                } else {
                    $where[] = 'created_by = :filter_created_by';
                    $params[':filter_created_by'] = $createdBy;
                }
            }
        }

        $club = trim((string)($filters['club'] ?? ''));
        if ($club !== '') {
            $where[] = 'club_level = :club';
            $params[':club'] = $club;
        }

        $status = trim((string)($filters['status'] ?? ''));
        if ($status !== '') {
            $where[] = 'status = :status';
            $params[':status'] = $status;
        }

        $sqlWhere = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
        $stmt = $pdo->prepare("SELECT * FROM rayan_purchase_requests {$sqlWhere} ORDER BY id DESC");
        foreach ($params as $key => $value) {
            $stmt->bindValue($key, $value);
        }
        $stmt->execute();
        $allRows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];

        $filteredRows = [];
        foreach ($allRows as $row) {
            $manualValues = $this->getManualRequestFormValues($row);
            $reportValues = $this->maskHiddenRequestAmountReportValues($this->buildReportFilterValues($row, $manualValues));
            if (!$this->rowMatchesReportFilters($row, $reportValues, $filters)) {
                continue;
            }
            $row['_manual_values'] = $manualValues;
            $row['_report_filter_values'] = $reportValues;
            $filteredRows[] = $row;
        }

        return $filteredRows;
    }

    private function buildRequestExportCellValues(array $row): array
    {
        $formValues = is_array($row['_manual_values'] ?? null) ? (array)$row['_manual_values'] : $this->getManualRequestFormValues($row);
        $source = (string)($row['source'] ?? 'webhook');
        $ownerLabel = $source === 'manual_site_form'
            ? $this->reportActorLabel((string)($row['created_by'] ?? ''), (string)($row['created_by_display'] ?? ''), 'دستی سایت')
            : 'وبهوک / فرم‌افزار';
        $submitterLabel = $this->reportActorLabel((string)($row['submitted_by'] ?? ''), (string)($row['submitted_by_display'] ?? ''), '-');
        $submittedAt = trim((string)($row['submitted_at'] ?? ''));

        $productDisplay = trim((string)($formValues['product_name'] ?? $row['product_name'] ?? ''));
        $installmentDisplay = trim((string)($formValues['installment_amount'] ?? $row['installment_amount'] ?? ''));
        $salesDisplay = trim((string)($formValues['sales_amount'] ?? ''));
        $purchaseDisplay = trim((string)($formValues['purchase_amount'] ?? ''));
        $postalDisplay = trim((string)($formValues['postal_code'] ?? $row['postal_code'] ?? ''));
        $sourceModeDisplay = trim((string)($formValues['source_mode'] ?? ''));
        $sellerStoreDisplay = trim((string)($formValues['seller_store_title'] ?? ''));
        $addressDisplay = trim((string)($formValues['address'] ?? $row['address'] ?? ''));

        return [
            'id' => (string)($row['id'] ?? ''),
            'created_at' => $this->formatJalaliDateTime((string)($row['created_at'] ?? '')),
            'owner' => $ownerLabel,
            'submitter' => $submitterLabel,
            'submitted_at' => $submittedAt !== '' ? $this->formatJalaliDateTime($submittedAt) : '',
            'plan' => (string)($row['club_level'] ?? ''),
            'final' => (string)($row['final_status'] ?? ''),
            'status' => $this->statusLabel((string)($row['status'] ?? '')),
            'customer' => trim((string)($formValues['buyer_name'] ?? $row['buyer_name'] ?? '')),
            'national' => (string)($row['national_id'] ?? ''),
            'mobile' => (string)($row['mobile'] ?? ''),
            'product' => $productDisplay,
            'installment_amount' => $installmentDisplay !== '' ? rayanFormatMoney($installmentDisplay) : '',
            'sales_amount' => $salesDisplay !== '' ? rayanFormatMoney($salesDisplay) : '',
            'purchase_amount' => $purchaseDisplay !== '' ? rayanFormatMoney($purchaseDisplay) : '',
            'postal' => $postalDisplay,
            'source_mode' => $sourceModeDisplay,
            'store' => $sellerStoreDisplay,
            'address' => $addressDisplay,
            'step' => (string)($row['last_step'] ?? ''),
            'error' => (string)($row['error_message'] ?? ''),
            'tracking' => (string)($row['minipay_tracking_code'] ?? ''),
        ];
    }

    public function streamRequestsExcel(array $filters = []): void
    {
        $this->assertCurrentUserCanDeleteReports();

        $columns = $this->requestReportColumns();
        $rows = $this->collectFilteredRequestsForReport($filters);
        $filename = 'rayan_requests_' . date('Y-m-d_H-i-s') . '.xls';

        while (ob_get_level() > 0) {
            ob_end_clean();
        }

        header('Content-Type: application/vnd.ms-excel; charset=UTF-8');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate');
        header('Pragma: public');

        echo "\xEF\xBB\xBF";
        echo '<!doctype html><html><head><meta charset="UTF-8">';
        echo '<style>table{border-collapse:collapse;font-family:Tahoma,Arial,sans-serif;font-size:11pt}th,td{border:1px solid #999;padding:6px 8px;mso-number-format:"\\@";white-space:nowrap}th{background:#e8eef8;font-weight:bold}.long{white-space:normal}</style>';
        echo '</head><body dir="rtl"><table>';
        echo '<thead><tr>';
        foreach ($columns as $title) {
            echo '<th>' . htmlspecialchars((string)$title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</th>';
        }
        echo '</tr></thead><tbody>';

        foreach ($rows as $row) {
            $values = $this->buildRequestExportCellValues($row);
            echo '<tr>';
            foreach ($columns as $key => $title) {
                $class = in_array($key, ['address', 'error', 'step'], true) ? ' class="long"' : '';
                echo '<td' . $class . '>' . htmlspecialchars((string)($values[$key] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</td>';
            }
            echo '</tr>';
        }

        echo '</tbody></table></body></html>';
        exit;
    }

    public function listRequests(array $filters = []): array
    {
        $pdo = $this->pdo();
        $where = [];
        $params = [];
        $scopeWhere = [];
        $scopeParams = [];

        $actor = $this->currentPanelUser();
        $isAdmin = (bool)$actor['is_admin'];
        $actorUsername = (string)$actor['username'];

        if (!$isAdmin) {
            if ($actorUsername === '') {
                $where[] = '1 = 0';
                $scopeWhere[] = '1 = 0';
            } else {
                $where[] = 'created_by = :scope_created_by';
                $params[':scope_created_by'] = $actorUsername;

                $scopeWhere[] = 'created_by = :scope_created_by';
                $scopeParams[':scope_created_by'] = $actorUsername;
            }
        } else {
            $createdBy = trim((string)($filters['created_by'] ?? ''));
            if ($createdBy !== '') {
                if ($createdBy === '__webhook__') {
                    $where[] = "COALESCE(NULLIF(created_by, ''), '') = ''";
                    $scopeWhere[] = "COALESCE(NULLIF(created_by, ''), '') = ''";
                } else {
                    $where[] = 'created_by = :filter_created_by';
                    $params[':filter_created_by'] = $createdBy;

                    $scopeWhere[] = 'created_by = :filter_created_by';
                    $scopeParams[':filter_created_by'] = $createdBy;
                }
            }
        }

        $club = trim((string)($filters['club'] ?? ''));
        if ($club !== '') {
            $where[] = 'club_level = :club';
            $params[':club'] = $club;
        }

        $status = trim((string)($filters['status'] ?? ''));
        if ($status !== '') {
            $where[] = 'status = :status';
            $params[':status'] = $status;
        }

        $page = max(1, (int)($filters['p'] ?? 1));
        $perPage = (int)($filters['per_page'] ?? 25);
        if (!in_array($perPage, [10, 25, 50, 100], true)) {
            $perPage = 25;
        }

        $sqlWhere = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
        $stmt = $pdo->prepare("SELECT * FROM rayan_purchase_requests {$sqlWhere} ORDER BY id DESC");
        foreach ($params as $key => $value) {
            $stmt->bindValue($key, $value);
        }
        $stmt->execute();
        $allRows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];

        $filteredRows = [];
        foreach ($allRows as $row) {
            $manualValues = $this->getManualRequestFormValues($row);
            $reportValues = $this->maskHiddenRequestAmountReportValues($this->buildReportFilterValues($row, $manualValues));
            if (!$this->rowMatchesReportFilters($row, $reportValues, $filters)) {
                continue;
            }
            $row['_manual_values'] = $manualValues;
            $row['_report_filter_values'] = $reportValues;
            $filteredRows[] = $row;
        }

        $total = count($filteredRows);
        $lastPage = max(1, (int)ceil($total / $perPage));
        if ($page > $lastPage) {
            $page = $lastPage;
        }
        $offset = ($page - 1) * $perPage;
        $rows = array_slice($filteredRows, $offset, $perPage);

        $scopeSqlWhere = $scopeWhere ? ('WHERE ' . implode(' AND ', $scopeWhere)) : '';

        $clubStmt = $pdo->prepare("SELECT COALESCE(NULLIF(club_level,''), 'بدون نوع') AS club_level, COUNT(*) AS cnt FROM rayan_purchase_requests {$scopeSqlWhere} GROUP BY COALESCE(NULLIF(club_level,''), 'بدون نوع') ORDER BY cnt DESC");
        $clubStmt->execute($scopeParams);
        $clubRows = $clubStmt->fetchAll(PDO::FETCH_ASSOC) ?: [];

        $statusStmt = $pdo->prepare("SELECT status, COUNT(*) AS cnt FROM rayan_purchase_requests {$scopeSqlWhere} GROUP BY status ORDER BY cnt DESC");
        $statusStmt->execute($scopeParams);
        $statusRows = $statusStmt->fetchAll(PDO::FETCH_ASSOC) ?: [];

        $userRows = [];
        if ($isAdmin) {
            $userRows = $pdo->query("
                SELECT
                    CASE
                        WHEN COALESCE(NULLIF(created_by_display, ''), NULLIF(created_by, '')) IS NOT NULL
                            THEN COALESCE(NULLIF(created_by_display, ''), created_by)
                        ELSE 'وبهوک / فرم‌افزار بدون کاربر'
                    END AS user_label,
                    CASE
                        WHEN COALESCE(NULLIF(created_by, ''), '') <> '' THEN created_by
                        ELSE '__webhook__'
                    END AS user_key,
                    COUNT(*) AS cnt
                FROM rayan_purchase_requests
                GROUP BY user_label, user_key
                ORDER BY cnt DESC, user_label ASC
            ")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        }

        return [
            'rows' => $rows,
            'club_counts' => $clubRows,
            'status_counts' => $statusRows,
            'user_counts' => $userRows,
            'is_admin_view' => $isAdmin,
            'pagination' => [
                'page' => $page,
                'per_page' => $perPage,
                'total' => $total,
                'last_page' => $lastPage,
                'from' => $total > 0 ? $offset + 1 : 0,
                'to' => min($offset + count($rows), $total),
            ],
        ];
    }

    public function deleteRequest(int $requestId, bool $deleteSubmission = true): array
    {
        if ($requestId <= 0) {
            throw new RuntimeException('شناسه درخواست برای حذف معتبر نیست.');
        }

        $pdo = $this->pdo();
        $request = rayanGetPurchaseRequestById($pdo, $requestId);
        $this->assertCurrentUserCanDeleteRequest($request);
        $formEntryId = (int)($request['form_entry_id'] ?? 0);
        $submissionId = (int)($request['submission_id'] ?? 0);

        $pdo->beginTransaction();
        try {
            if ($formEntryId > 0) {
                $stmt = $pdo->prepare('DELETE FROM rayan_hesaban_invoice_docs WHERE form_entry_id = :form_entry_id OR request_id = :request_id');
                $stmt->execute([':form_entry_id' => $formEntryId, ':request_id' => $requestId]);
            } else {
                $stmt = $pdo->prepare('DELETE FROM rayan_hesaban_invoice_docs WHERE request_id = :request_id');
                $stmt->execute([':request_id' => $requestId]);
            }

            $stmt = $pdo->prepare('DELETE FROM rayan_purchase_requests WHERE id = :id LIMIT 1');
            $stmt->execute([':id' => $requestId]);

            if ($deleteSubmission && $submissionId > 0) {
                $stmt = $pdo->prepare('DELETE FROM forms_submission_params WHERE submission_id = :submission_id');
                $stmt->execute([':submission_id' => $submissionId]);

                $stmt = $pdo->prepare('DELETE FROM forms_submissions WHERE id = :submission_id LIMIT 1');
                $stmt->execute([':submission_id' => $submissionId]);
            }

            $pdo->commit();
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }

        rayanWebhookLog($this->config, 'درخواست از پنل حذف شد', [
            'request_id' => $requestId,
            'form_entry_id' => $formEntryId,
            'submission_id' => $submissionId,
        ]);

        return [
            'message' => 'درخواست #' . $requestId . ' با موفقیت حذف شد.',
            'request_id' => $requestId,
        ];
    }

    public function deleteRequests(array $requestIds, bool $deleteSubmission = true): array
    {
        $ids = [];
        foreach ($requestIds as $id) {
            $id = (int)$id;
            if ($id > 0) {
                $ids[$id] = $id;
            }
        }
        if (!$ids) {
            throw new RuntimeException('هیچ درخواستی برای حذف انتخاب نشده است.');
        }

        $deleted = 0;
        $errors = [];
        foreach (array_values($ids) as $id) {
            try {
                $this->deleteRequest($id, $deleteSubmission);
                $deleted++;
            } catch (Throwable $e) {
                $errors[] = '#' . $id . ': ' . $e->getMessage();
            }
        }

        if ($deleted === 0 && $errors) {
            throw new RuntimeException('حذف انجام نشد: ' . implode(' | ', $errors));
        }

        $message = $deleted . ' درخواست حذف شد.';
        if ($errors) {
            $message .= ' خطاها: ' . implode(' | ', $errors);
        }

        return [
            'message' => $message,
            'deleted' => $deleted,
            'errors' => $errors,
        ];
    }

    private function requestHasExternalClubSuccess(array $request): bool
    {
        if (trim((string)($request['minipay_tracking_code'] ?? '')) !== ''
            || (int)($request['minipay_invoice_id'] ?? 0) > 0
            || (int)($request['minipay_transaction_id'] ?? 0) > 0
            || ((string)($request['status'] ?? '') === 'external_done')) {
            return true;
        }

        // در مسیر دومرحله‌ای، status=completed یعنی مرحله اول هم انجام شده است؛
        // اگر فاکتور حسابان‌وب هم وجود داشته باشد یعنی هر دو مرحله کامل شده‌اند.
        if ((string)($request['status'] ?? '') === 'completed'
            && in_array(rayanResolveRequestProvider($this->config, (string)($request['club_level'] ?? '')), ['rayan', 'minipay_direct'], true)) {
            return true;
        }

        return false;
    }

    public function requestHasSuccessfulHesabanInvoices(array $request): bool
    {
        $formEntryId = (int)($request['form_entry_id'] ?? 0);
        if ($formEntryId <= 0) {
            return false;
        }

        return rayanHasSuccessfulHesabanInvoices($this->pdo(), $formEntryId);
    }

    private function assertCurrentUserCanStartClubStageOne(array $request): void
    {
        $actor = $this->currentPanelUser();
        if (!empty($actor['is_admin'])) {
            return;
        }
        if (!$this->requestBelongsToCurrentUser($request)) {
            throw new RuntimeException('ثبت مرحله اول فقط برای ادمین یا ایجادکننده همان فرم مجاز است.');
        }
    }

    public function processClubStageOne(int $requestId): array
    {
        $pdo = $this->pdo();
        $request = rayanGetPurchaseRequestById($pdo, $requestId);
        $this->assertCurrentUserCanUseRequest($request);

        if (in_array((string)$request['status'], ['processing'], true)) {
            throw new RuntimeException('این درخواست در حال پردازش است.');
        }

        $provider = rayanResolveRequestProvider($this->config, (string)($request['club_level'] ?? ''));
        if (!in_array($provider, ['rayan', 'minipay_direct'], true)) {
            throw new RuntimeException('ثبت مرحله اول فقط برای طرح‌های دومرحله‌ای است.');
        }

        $formEntryId = (int)($request['form_entry_id'] ?? 0);
        if ($formEntryId > 0 && rayanHasSuccessfulHesabanInvoices($pdo, $formEntryId) && $this->requestHasExternalClubSuccess($request)) {
            rayanUpdatePurchaseRequest($pdo, $requestId, [
                'status' => 'completed',
                'last_step' => 'مرحله اول و دوم کامل شده است',
                'error_message' => null,
                'completed_at' => (string)($request['completed_at'] ?? '') !== '' ? (string)$request['completed_at'] : date('Y-m-d H:i:s'),
            ]);
            return [
                'message' => 'این درخواست قبلاً کامل شده است.',
                'request' => rayanGetPurchaseRequestById($pdo, $requestId),
            ];
        }

        if ($this->requestHasExternalClubSuccess($request)) {
            return [
                'message' => 'مرحله اول قبلاً انجام شده است؛ برای ادامه، ادمین باید ثبت مرحله دوم را بزند.',
                'request' => $request,
            ];
        }

        $this->assertCurrentUserCanStartClubStageOne($request);
        rayanStartPurchaseProcessForRequest($pdo, $this->config, $requestId);
        $request = rayanGetPurchaseRequestById($pdo, $requestId);

        // اگر مسیر مرحله اول همان لحظه status=completed برگرداند،
        // آن را به «مرحله اول انجام شد» تبدیل می‌کنیم تا نبودن فاکتور حسابان‌وب باعث ناموفق/تکمیل اشتباه نشود.
        if ((string)($request['status'] ?? '') === 'completed') {
            $formEntryId = (int)($request['form_entry_id'] ?? 0);
            $hasHesaban = $formEntryId > 0 && rayanHasSuccessfulHesabanInvoices($pdo, $formEntryId);
            if (!$hasHesaban) {
                rayanUpdatePurchaseRequest($pdo, $requestId, [
                    'status' => 'external_done',
                    'last_step' => 'مرحله اول با موفقیت انجام شد؛ مرحله دوم جداگانه توسط مدیر قابل انجام است',
                    'error_message' => null,
                ]);
                $request = rayanGetPurchaseRequestById($pdo, $requestId);
            }
        }

        return [
            'message' => 'ثبت مرحله اول انجام شد. اگر درخواست منتظر کد تایید است، کد OTP را وارد کنید. وضعیت فعلی: ' . $this->statusLabel((string)$request['status']),
            'request' => $request,
        ];
    }

    public function processClubStageTwo(int $requestId): array
    {
        $pdo = $this->pdo();
        $request = rayanGetPurchaseRequestById($pdo, $requestId);
        $this->assertCurrentUserCanUseRequest($request);
        $this->assertCurrentUserIsAdminForFinalSubmission();

        if (in_array((string)$request['status'], ['processing'], true)) {
            throw new RuntimeException('این درخواست در حال پردازش است.');
        }

        $provider = rayanResolveRequestProvider($this->config, (string)($request['club_level'] ?? ''));
        if (!in_array($provider, ['rayan', 'minipay_direct'], true)) {
            throw new RuntimeException('ثبت مرحله دوم فقط برای طرح‌های دومرحله‌ای است.');
        }

        $formEntryId = (int)($request['form_entry_id'] ?? 0);
        if ($formEntryId > 0 && rayanHasSuccessfulHesabanInvoices($pdo, $formEntryId)) {
            $hasExternalStageOne = $this->requestHasExternalClubSuccess($request);
            rayanUpdatePurchaseRequest($pdo, $requestId, [
                'status' => $hasExternalStageOne ? 'completed' : 'hesaban_done',
                'last_step' => $hasExternalStageOne
                    ? 'مرحله اول و دوم کامل شده است'
                    : 'مرحله دوم قبلاً انجام شده است',
                'error_message' => null,
                'completed_at' => $hasExternalStageOne
                    ? ((string)($request['completed_at'] ?? '') !== '' ? (string)$request['completed_at'] : date('Y-m-d H:i:s'))
                    : null,
            ]);
            return [
                'message' => $hasExternalStageOne ? 'این درخواست قبلاً کامل شده است.' : 'مرحله دوم قبلاً انجام شده است؛ مرحله اول هنوز می‌تواند جداگانه انجام شود.',
                'request' => rayanGetPurchaseRequestById($pdo, $requestId),
            ];
        }

        // مرحله دوم مستقل از مرحله اول است: مدیر می‌تواند فاکتورهای حسابان‌وب را جداگانه ثبت کند،
        // حتی اگر مرحله اول هنوز انجام نشده باشد.
        $this->rememberFinalSubmitter($pdo, $requestId);
        $request = $this->processClubHesabanIfNeeded($pdo, $requestId);

        return [
            'message' => 'ثبت مرحله دوم انجام شد و فاکتورهای خرید و فروش حسابان‌وب بررسی/ثبت شدند. وضعیت فعلی: ' . $this->statusLabel((string)$request['status']),
            'request' => $request,
        ];
    }

    public function processRequest(int $requestId): array
    {
        $pdo = $this->pdo();
        $request = rayanGetPurchaseRequestById($pdo, $requestId);
        $this->assertCurrentUserCanUseRequest($request);

        if (in_array((string)$request['status'], ['processing'], true)) {
            throw new RuntimeException('این درخواست در حال پردازش است.');
        }

        $provider = rayanResolveRequestProvider($this->config, (string)($request['club_level'] ?? ''));

        if (in_array($provider, ['rayan', 'minipay_direct'], true)) {
            if ($this->requestHasExternalClubSuccess($request)) {
                return $this->processClubStageTwo($requestId);
            }
            return $this->processClubStageOne($requestId);
        }

        $this->assertCurrentUserIsAdminForFinalSubmission();
        $this->rememberFinalSubmitter($pdo, $requestId);
        $request = rayanGetPurchaseRequestById($pdo, $requestId);

        if ($provider === 'hesaban_only') {
            $formEntryId = (int)($request['form_entry_id'] ?? 0);
            if ($formEntryId > 0 && rayanHasSuccessfulHesabanInvoices($pdo, $formEntryId)) {
                rayanUpdatePurchaseRequest($pdo, $requestId, [
                    'status' => 'completed',
                    'last_step' => 'فاکتور خرید و فروش حسابان‌وب قبلاً ثبت شده است',
                    'error_message' => null,
                    'completed_at' => (string)($request['completed_at'] ?? '') !== '' ? (string)$request['completed_at'] : date('Y-m-d H:i:s'),
                ]);
                $request = rayanGetPurchaseRequestById($pdo, $requestId);
                return [
                    'message' => 'فاکتورهای حسابان‌وب قبلاً ثبت شده‌اند و نیازی به ثبت دوباره نیست.',
                    'request' => $request,
                ];
            }

            rayanUpdatePurchaseRequest($pdo, $requestId, [
                'status' => 'processing',
                'last_step' => 'در حال ثبت فاکتور خرید و سپس فروش حسابان‌وب',
                'error_message' => null,
            ]);

            try {
                $this->tryCreateHesabanInvoiceForRequest($pdo, $request);
                rayanUpdatePurchaseRequest($pdo, $requestId, [
                    'status' => 'completed',
                    'last_step' => 'فاکتور خرید و فروش حسابان‌وب ثبت شد',
                    'error_message' => null,
                    'completed_at' => date('Y-m-d H:i:s'),
                ]);
            } catch (Throwable $e) {
                rayanUpdatePurchaseRequest($pdo, $requestId, [
                    'status' => 'failed',
                    'last_step' => 'خطا در ثبت فاکتور حسابان‌وب',
                    'error_message' => $e->getMessage(),
                ]);
                throw $e;
            }

            $request = rayanGetPurchaseRequestById($pdo, $requestId);
            return [
                'message' => 'ثبت فاکتورهای حسابان‌وب انجام شد. وضعیت فعلی: ' . $this->statusLabel((string)$request['status']),
                'request' => $request,
            ];
        }

        throw new RuntimeException('برای این نوع طرح فروش مسیر پردازش تعریف نشده است: ' . (string)($request['club_level'] ?? ''));
    }

    public function retryRequest(int $requestId): array
    {
        $pdo = $this->pdo();
        $request = rayanGetPurchaseRequestById($pdo, $requestId);
        $this->assertCurrentUserCanUseRequest($request);
        $provider = rayanResolveRequestProvider($this->config, (string)($request['club_level'] ?? ''));

        if (in_array($provider, ['rayan', 'minipay_direct'], true)) {
            if ($this->requestHasExternalClubSuccess($request)) {
                $this->assertCurrentUserIsAdminForFinalSubmission();
                return $this->processRequest($requestId);
            }
            $this->assertCurrentUserCanStartClubStageOne($request);
        } else {
            $this->assertCurrentUserIsAdminForFinalSubmission();
        }

        rayanUpdatePurchaseRequest($pdo, $requestId, [
            'status' => 'new',
            'last_step' => in_array($provider, ['rayan', 'minipay_direct'], true) ? 'شروع مجدد مرحله اول از پنل' : 'شروع تلاش مجدد از پنل',
            'error_message' => null,
            'otp_context_json' => null,
            'otp_requested_at' => null,
            'completed_at' => null,
        ]);
        return $this->processRequest($requestId);
    }

    public function submitOtp(int $requestId, string $otpCode): array
    {
        $otpCode = rayanDigitsOnly($otpCode);
        if ($otpCode === '') {
            throw new RuntimeException('کد تایید خالی است.');
        }

        $pdo = $this->pdo();
        $request = rayanGetPurchaseRequestById($pdo, $requestId);
        $this->assertCurrentUserCanUseRequest($request);
        $provider = rayanResolveRequestProvider($this->config, (string)($request['club_level'] ?? ''));
        if (in_array($provider, ['rayan', 'minipay_direct'], true)) {
            $this->assertCurrentUserCanStartClubStageOne($request);
        } else {
            $this->assertCurrentUserIsAdminForFinalSubmission();
            $this->rememberFinalSubmitter($pdo, $requestId);
        }

        rayanSubmitOtpForRequest($pdo, $this->config, $requestId, $otpCode);
        $request = rayanGetPurchaseRequestById($pdo, $requestId);

        if (in_array($provider, ['rayan', 'minipay_direct'], true) && (string)($request['status'] ?? '') === 'completed') {
            $formEntryId = (int)($request['form_entry_id'] ?? 0);
            $hasHesaban = $formEntryId > 0 && rayanHasSuccessfulHesabanInvoices($pdo, $formEntryId);
            rayanUpdatePurchaseRequest($pdo, $requestId, [
                'status' => $hasHesaban ? 'completed' : 'external_done',
                'last_step' => $hasHesaban
                    ? 'مرحله اول و دوم با موفقیت انجام شد'
                    : 'مرحله اول با موفقیت انجام شد؛ مرحله دوم جداگانه توسط مدیر قابل انجام است',
                'error_message' => null,
            ]);
            $request = rayanGetPurchaseRequestById($pdo, $requestId);
        }

        return [
            'message' => 'کد تایید ارسال شد. وضعیت فعلی: ' . $this->statusLabel((string)$request['status']),
            'request' => $request,
        ];
    }

    private function tryCreateHesabanInvoiceForRequest(PDO $pdo, array $request): void
    {
        $submissionId = (int)($request['submission_id'] ?? 0);
        $formEntryId = (int)($request['form_entry_id'] ?? 0);
        if ($submissionId <= 0 || $formEntryId <= 0) {
            throw new RuntimeException('شناسه فرم یا submission برای ثبت حسابان‌وب معتبر نیست.');
        }

        $stmt = $pdo->prepare('SELECT raw_json, final_status FROM forms_submissions WHERE id = :id LIMIT 1');
        $stmt->execute([':id' => $submissionId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row || trim((string)($row['raw_json'] ?? '')) === '') {
            throw new RuntimeException('raw_json فرم برای ثبت حسابان‌وب پیدا نشد.');
        }

        $payload = json_decode((string)$row['raw_json'], true);
        if (!is_array($payload) || !isset($payload['form']) || !is_array($payload['form'])) {
            throw new RuntimeException('payload فرم برای ثبت حسابان‌وب معتبر نیست.');
        }
        $form = $payload['form'];
        $params = isset($form['params']) && is_array($form['params']) ? $form['params'] : [];

        $result = rayanTryCreateHesabanInvoiceForApprovedFormSafely(
            $pdo,
            $this->config,
            $formEntryId,
            $submissionId,
            (int)$request['id'],
            (string)($row['final_status'] ?? $request['final_status'] ?? ''),
            $form,
            $params
        );

        if ((string)($result['status'] ?? '') !== 'success') {
            throw new RuntimeException((string)($result['error'] ?? 'ثبت حسابان‌وب موفق نبود.'));
        }

        $results = is_array($result['results'] ?? null) ? $result['results'] : [];
        $successStatuses = ['success', 'skipped_duplicate_success'];

        if (empty($results['purchase']) || !in_array((string)($results['purchase']['status'] ?? ''), $successStatuses, true)) {
            throw new RuntimeException('فاکتور خرید حسابان‌وب با موفقیت ثبت نشد.');
        }
        if (empty($results['sales']) || !in_array((string)($results['sales']['status'] ?? ''), $successStatuses, true)) {
            throw new RuntimeException('فاکتور فروش حسابان‌وب با موفقیت ثبت نشد.');
        }
    }

    private function processClubHesabanIfNeeded(PDO $pdo, int $requestId): array
    {
        $request = rayanGetPurchaseRequestById($pdo, $requestId);
        $this->assertCurrentUserCanUseRequest($request);
        $provider = rayanResolveRequestProvider($this->config, (string)($request['club_level'] ?? ''));
        if (!in_array($provider, ['rayan', 'minipay_direct'], true)) {
            return $request;
        }

        $formEntryId = (int)($request['form_entry_id'] ?? 0);
        if ($formEntryId > 0 && rayanHasSuccessfulHesabanInvoices($pdo, $formEntryId)) {
            return $request;
        }

        try {
            $this->tryCreateHesabanInvoiceForRequest($pdo, $request);

            $hasExternalStageOne = $this->requestHasExternalClubSuccess($request);
            rayanUpdatePurchaseRequest($pdo, $requestId, [
                'status' => $hasExternalStageOne ? 'completed' : 'hesaban_done',
                'last_step' => $hasExternalStageOne
                    ? 'مرحله اول و دوم با موفقیت انجام شد'
                    : 'مرحله دوم با موفقیت انجام شد',
                'error_message' => null,
                'completed_at' => $hasExternalStageOne
                    ? ((string)($request['completed_at'] ?? '') !== '' ? (string)$request['completed_at'] : date('Y-m-d H:i:s'))
                    : null,
            ]);

            return rayanGetPurchaseRequestById($pdo, $requestId);
        } catch (Throwable $e) {
            $hasExternalStageOne = $this->requestHasExternalClubSuccess($request);
            rayanUpdatePurchaseRequest($pdo, $requestId, [
                'status' => $hasExternalStageOne ? 'external_done' : 'failed',
                'last_step' => $hasExternalStageOne
                    ? 'مرحله اول انجام شده، خطا در ثبت مرحله دوم حسابان‌وب'
                    : 'خطا در ثبت مرحله دوم حسابان‌وب',
                'error_message' => $e->getMessage(),
                'completed_at' => (string)($request['completed_at'] ?? '') !== '' ? (string)$request['completed_at'] : null,
            ]);

            throw new RuntimeException('ثبت مرحله دوم حسابان‌وب ناموفق بود: ' . $e->getMessage());
        }
    }

    private function creditInquiryReportsPath(): string
    {
        $storage = app_path('storage');
        ensure_dir($storage);
        return rtrim($storage, '/\\') . '/rayan_credit_inquiry_reports.json';
    }

    private function writeCreditInquiryReports(array $reports): void
    {
        $file = $this->creditInquiryReportsPath();
        ensure_dir(dirname($file));
        file_put_contents(
            $file,
            json_encode(array_values($reports), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT),
            LOCK_EX
        );
    }

    private function loadCreditInquiryReports(bool $savePurge = true): array
    {
        $file = $this->creditInquiryReportsPath();
        if (!is_file($file)) {
            return [];
        }

        $raw = (string)file_get_contents($file);
        $data = json_decode($raw, true);
        if (!is_array($data)) {
            if ($savePurge) {
                $this->writeCreditInquiryReports([]);
            }
            return [];
        }

        $cutoff = time() - 86400;
        $reports = [];
        $changed = false;
        foreach ($data as $row) {
            if (!is_array($row)) {
                $changed = true;
                continue;
            }
            $createdAt = trim((string)($row['created_at'] ?? ''));
            $createdTs = $createdAt !== '' ? strtotime($createdAt) : false;
            if ($createdTs === false || $createdTs < $cutoff) {
                $changed = true;
                continue;
            }
            $reports[] = $row;
        }

        usort($reports, static function (array $a, array $b): int {
            return strcmp((string)($b['created_at'] ?? ''), (string)($a['created_at'] ?? ''));
        });

        if ($changed && $savePurge) {
            $this->writeCreditInquiryReports($reports);
        }

        return $reports;
    }

    public function saveCreditInquiryReport(array $result): array
    {
        $actor = $this->currentPanelUser();
        $now = date('Y-m-d H:i:s');
        $username = rayanNormalizeSpaces((string)($actor['username'] ?? ''));
        $displayName = rayanNormalizeSpaces((string)($actor['display_name'] ?? $username));
        if ($displayName === '') {
            $displayName = $username;
        }

        $report = [
            'id' => date('YmdHis') . '_' . bin2hex(random_bytes(4)),
            'created_at' => $now,
            'username' => $username,
            'display_name' => $displayName,
            'national_id' => (string)($result['national_id'] ?? ''),
            'mobile' => (string)($result['mobile'] ?? ''),
            'credit_limit' => (string)($result['credit_limit'] ?? ''),
            'monthly_estimate' => (string)($result['monthly_estimate'] ?? ''),
            'club_purchase_power' => (string)($result['club_purchase_power'] ?? ''),
            'final_url' => (string)($result['final_url'] ?? ''),
        ];

        $reports = $this->loadCreditInquiryReports(false);
        $reports[] = $report;

        $cutoff = time() - 86400;
        $reports = array_values(array_filter($reports, static function (array $row) use ($cutoff): bool {
            $ts = strtotime((string)($row['created_at'] ?? ''));
            return $ts !== false && $ts >= $cutoff;
        }));

        usort($reports, static function (array $a, array $b): int {
            return strcmp((string)($b['created_at'] ?? ''), (string)($a['created_at'] ?? ''));
        });

        $this->writeCreditInquiryReports($reports);
        return $report;
    }

    public function listCreditInquiryReports(): array
    {
        $reports = $this->loadCreditInquiryReports(true);
        $actor = $this->currentPanelUser();
        $isAdmin = !empty($actor['is_admin']);
        $username = (string)($actor['username'] ?? '');

        if (!$isAdmin) {
            $reports = array_values(array_filter($reports, static function (array $row) use ($username): bool {
                return $username !== '' && (string)($row['username'] ?? '') === $username;
            }));
        }

        foreach ($reports as &$row) {
            $createdAt = (string)($row['created_at'] ?? '');
            $createdTs = strtotime($createdAt);
            $expiresAt = $createdTs !== false ? date('Y-m-d H:i:s', $createdTs + 86400) : '';
            $row['created_at_jalali'] = $this->formatJalaliDateTime($createdAt);
            $row['expires_at_jalali'] = $expiresAt !== '' ? $this->formatJalaliDateTime($expiresAt) : '-';
            if (trim((string)($row['club_purchase_power'] ?? '')) === '') {
                $creditInt = $this->moneyTextToInt((string)($row['credit_limit'] ?? ''));
                $clubPurchasePower = $creditInt > 0 ? (int)floor($creditInt / 1.92) : 0;
                $row['club_purchase_power'] = $clubPurchasePower > 0 ? number_format($clubPurchasePower) : 'یافت نشد';
            }
            $row['actor_label'] = $this->reportActorLabel(
                (string)($row['username'] ?? ''),
                (string)($row['display_name'] ?? ''),
                '-'
            );
        }
        unset($row);

        return $reports;
    }

    public function streamCreditInquiryReportsExcel(): void
    {
        $this->assertCurrentUserCanDeleteReports();
        $reports = $this->listCreditInquiryReports();
        $filename = 'rayan_inquiries_' . date('Y-m-d_H-i-s') . '.xls';
        $columns = [
            'idx' => '#',
            'actor_label' => 'کاربر',
            'created_at_jalali' => 'تاریخ و زمان',
            'national_id' => 'کد ملی',
            'mobile' => 'موبایل',
            'credit_limit' => 'سقف اعتبار',
            'monthly_estimate' => 'سقف ماهانه',
            'club_purchase_power' => 'توان خرید کلوپ',
            'expires_at_jalali' => 'حذف خودکار',
        ];

        while (ob_get_level() > 0) {
            ob_end_clean();
        }

        header('Content-Type: application/vnd.ms-excel; charset=UTF-8');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate');
        header('Pragma: public');

        echo "ï»¿";
        echo '<!doctype html><html><head><meta charset="UTF-8">';
        echo '<style>table{border-collapse:collapse;font-family:Tahoma,Arial,sans-serif;font-size:11pt}th,td{border:1px solid #999;padding:6px 8px;mso-number-format:"\@";white-space:nowrap}th{background:#e8eef8;font-weight:bold}</style>';
        echo '</head><body dir="rtl"><table><thead><tr>';
        foreach ($columns as $title) {
            echo '<th>' . htmlspecialchars((string)$title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</th>';
        }
        echo '</tr></thead><tbody>';

        foreach ($reports as $idx => $report) {
            echo '<tr>';
            foreach ($columns as $key => $title) {
                $value = $key === 'idx' ? (string)($idx + 1) : (string)($report[$key] ?? '');
                echo '<td>' . htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</td>';
            }
            echo '</tr>';
        }

        echo '</tbody></table></body></html>';
        exit;
    }

    public function runCreditInquiry(string $nationalId, string $mobile): array
    {
        $nationalId = rayanDigitsOnly($nationalId);
        $mobile = rayanDigitsOnly($mobile);
        if (strlen($mobile) === 10 && str_starts_with($mobile, '9')) {
            $mobile = '0' . $mobile;
        }
        if (strlen($nationalId) !== 10) {
            throw new RuntimeException('کد ملی باید ۱۰ رقم باشد.');
        }
        if (strlen($mobile) !== 11 || !str_starts_with($mobile, '09')) {
            throw new RuntimeException('شماره موبایل باید ۱۱ رقم و با 09 شروع شود.');
        }

        $rc = $this->config['rayan'] ?? [];
        $startUrl = trim((string)($rc['start_url'] ?? 'https://rayankimia.ir/customer/new_customer_minipay/'));
        $username = trim((string)($rc['username'] ?? ''));
        $password = (string)($rc['password'] ?? '');
        $clubText = trim((string)($rc['club_link_text'] ?? 'طرح باشگاه مشتریان'));
        if ($username === '' || $password === '') {
            throw new RuntimeException('نام کاربری یا رمز عبور سامانه استعلام تنظیم نشده است.');
        }

        $debugDir = rtrim((string)($this->config['paths']['runtime_dir'] ?? app_path('storage/rayan_runtime')), '/\\') . '/credit_inquiry';
        rayanEnsureDir($debugDir);
        $hash = md5($nationalId . '|' . $mobile . '|' . microtime(true));
        $cookieFile = $debugDir . '/cookie_' . $hash . '.txt';

        $res = rayanHttpRequest($startUrl, null, $cookieFile);
        $loginForm = rayanFindFormByQuery($res['body'], '//form[.//*[@id="id_username"] and .//*[@id="id_password"]]', $res['url']);
        if (!$loginForm) {
            throw new RuntimeException('فرم ورود سامانه پیدا نشد.');
        }
        rayanSetFieldById($loginForm, 'id_username', $username);
        rayanSetFieldById($loginForm, 'id_password', $password);
        $res = rayanHttpRequest($loginForm['action'], $loginForm['fields'], $cookieFile, ['Referer: ' . $startUrl]);

        $clubUrl = rayanFindHrefByText($res['body'], $clubText, $res['url']);
        if ($clubUrl) {
            $res = rayanHttpRequest($clubUrl, null, $cookieFile, ['Referer: ' . $res['url']]);
        }

        $queryForm = rayanFindFormByQuery($res['body'], '//form[.//*[@id="id_n_id"] and .//*[@id="id_mobile"]]', $res['url']);
        if (!$queryForm) {
            throw new RuntimeException('فرم استعلام کلوپ پیدا نشد.');
        }
        rayanSetFieldById($queryForm, 'id_n_id', $nationalId);
        rayanSetFieldById($queryForm, 'id_mobile', $mobile);
        $res = rayanHttpRequest($queryForm['action'], $queryForm['fields'], $cookieFile, ['Referer: ' . $res['url']]);
        if ((int)$res['status'] >= 400) {
            throw new RuntimeException('استعلام با خطای HTTP انجام شد: ' . (int)$res['status']);
        }

        [$credit] = rayanExtractCreditInfo($res['body']);
        $creditInt = $this->moneyTextToInt((string)$credit);
        $monthly = $creditInt > 0 ? (int)floor($creditInt / 12) : 0;
        $clubPurchasePower = $creditInt > 0 ? (int)floor($creditInt / 1.92) : 0;

        return [
            'national_id' => $nationalId,
            'mobile' => $mobile,
            'credit_limit' => $credit ?: 'یافت نشد',
            'monthly_estimate' => $monthly > 0 ? number_format($monthly) : 'یافت نشد',
            'club_purchase_power' => $clubPurchasePower > 0 ? number_format($clubPurchasePower) : 'یافت نشد',
            'final_url' => $res['url'],
        ];
    }

    public function statusLabel(string $status): string
    {
        return match ($status) {
            'new' => 'آماده ثبت',
            'processing' => 'در حال پردازش',
            'awaiting_otp' => 'منتظر کد تایید',
            'external_done' => 'مرحله اول انجام شد',
            'hesaban_done' => 'مرحله دوم انجام شد',
            'completed' => 'تکمیل شده',
            'failed' => 'ناموفق',
            'invalid' => 'اطلاعات ناقص',
            'ignored' => 'نادیده گرفته شده',
            default => $status,
        };
    }

    public function providerLabel(array $request): string
    {
        $route = rayanResolveClubRoute($this->config, (string)($request['club_level'] ?? ''));
        $provider = $route ? (string)($route['provider'] ?? '') : '';
        if ($provider === 'rayan') return 'طرح دومرحله‌ای سطح ۱';
        if ($provider === 'minipay_direct') return 'طرح دومرحله‌ای سطح ۲ یا قسطانو';
        if (rayanResolveRequestProvider($this->config, (string)($request['club_level'] ?? '')) === 'hesaban_only') return 'حسابان‌وب / سایر طرح‌ها';
        return 'نامشخص';
    }

    private function moneyTextToInt(string $text): int
    {
        $text = rayanFaToEn($text);
        $digits = preg_replace('/\D+/', '', $text);
        if ($digits === '') return 0;
        return (int)$digits;
    }
}
