<?php

/**
 * Resolve config / subscription input to live panel stats (XUI v3.2.7 + Marzban + legacy).
 */
class ConfigLookupService
{
    public static function looksLikeConfigInput(string $text): bool
    {
        $text = trim($text);
        if ($text === '') {
            return false;
        }
        if (preg_match('/^(vmess|vless|trojan):\/\//i', $text)) {
            return true;
        }
        if (preg_match('~(?:https?://)?[^\s]*/(?:sub|subs)/([^/?#\s]+)~i', $text)) {
            return true;
        }
        if (preg_match('~search\.php\?id=([^&\s]+)~i', $text)) {
            return true;
        }
        if (preg_match('/[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-(8|9|a|b)[a-f0-9]{3}-[a-f0-9]{12}/i', $text)) {
            return true;
        }
        return (bool) preg_match('/^[a-zA-Z0-9]{5,32}$/', $text);
    }

    /**
     * @return array{type:string,value:string,marzbanText?:string}|null
     */
    public static function parseInput(string $text): ?array
    {
        $raw = trim($text);
        if ($raw === '') {
            return null;
        }

        if (preg_match('/^vmess:\/\/(.*)/i', $raw, $match)) {
            $decoded = json_decode(base64_decode($match[1], true) ?: '', true);
            if (!is_array($decoded) || empty($decoded['id'])) {
                return null;
            }
            return ['type' => 'uuid', 'value' => (string) $decoded['id'], 'marzbanText' => $match[1]];
        }
        if (preg_match('/^vless:\/\/(.*?)\@/i', $raw, $match)) {
            return ['type' => 'uuid', 'value' => $match[1], 'marzbanText' => $match[1]];
        }
        if (preg_match('/^trojan:\/\/(.*?)\@/i', $raw, $match)) {
            return ['type' => 'uuid', 'value' => $match[1], 'marzbanText' => $match[1]];
        }
        if (preg_match('~(?:https?://)?[^\s]*/(?:sub|subs)/([^/?#\s]+)~i', $raw, $match)) {
            return ['type' => 'subId', 'value' => urldecode($match[1])];
        }
        if (preg_match('~search\.php\?id=([^&\s]+)~i', $raw, $match)) {
            return ['type' => 'token', 'value' => urldecode($match[1])];
        }
        if (preg_match('/[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-(8|9|a|b)[a-f0-9]{3}-[a-f0-9]{12}/i', $raw, $match)) {
            return ['type' => 'uuid', 'value' => $match[0], 'marzbanText' => $match[0]];
        }
        if (preg_match('/^[a-zA-Z0-9]{5,32}$/', $raw)) {
            return ['type' => 'token', 'value' => $raw];
        }

        return null;
    }

    /**
     * @return array<string, mixed>|null
     */
    public static function lookup(string $text): ?array
    {
        $parsed = self::parseInput($text);
        if (!$parsed) {
            return null;
        }

        $orderHint = self::findOrderHint($parsed['type'], $parsed['value']);
        if ($orderHint) {
            $fromPanel = self::lookupOnServer(
                (int) $orderHint['server_id'],
                (string) $orderHint['server_type'],
                $parsed,
                (string) $orderHint['remark'],
                (string) ($orderHint['uuid'] ?? ''),
                (int) ($orderHint['inbound_id'] ?? 0)
            );
            if ($fromPanel) {
                return $fromPanel;
            }
        }

        global $connection;
        $stmt = $connection->prepare('SELECT * FROM `server_config`');
        $stmt->execute();
        $servers = $stmt->get_result();
        $stmt->close();

        while ($row = $servers->fetch_assoc()) {
            $result = self::lookupOnServer(
                (int) $row['id'],
                (string) $row['type'],
                $parsed,
                is_array($orderHint) ? (string) ($orderHint['remark'] ?? '') : '',
                is_array($orderHint) ? (string) ($orderHint['uuid'] ?? '') : '',
                is_array($orderHint) ? (int) ($orderHint['inbound_id'] ?? 0) : 0
            );
            if ($result) {
                return $result;
            }
        }

        return null;
    }

    /**
     * @return array<string, mixed>|null
     */
    private static function findOrderHint(string $type, string $value): ?array
    {
        global $connection;
        if ($type === 'uuid' || $type === 'token') {
            $stmt = $connection->prepare(
                'SELECT o.*, s.`type` AS server_type
                 FROM `orders_list` o
                 INNER JOIN `server_config` s ON s.`id` = o.`server_id`
                 WHERE o.`uuid` = ? OR o.`token` = ? OR o.`uniqid` = ?
                 LIMIT 1'
            );
            $stmt->bind_param('sss', $value, $value, $value);
            $stmt->execute();
            $row = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $row ?: null;
        }
        if ($type === 'subId') {
            $like = '%' . $value . '%';
            $stmt = $connection->prepare(
                'SELECT o.*, s.`type` AS server_type
                 FROM `orders_list` o
                 INNER JOIN `server_config` s ON s.`id` = o.`server_id`
                 WHERE o.`token` = ? OR o.`link` LIKE ?
                 LIMIT 1'
            );
            $stmt->bind_param('ss', $value, $like);
            $stmt->execute();
            $row = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $row ?: null;
        }
        return null;
    }

    /**
     * @param array{type:string,value:string,marzbanText?:string} $parsed
     * @return array<string, mixed>|null
     */
    private static function lookupOnServer(
        int $serverId,
        string $serverType,
        array $parsed,
        string $remarkHint = '',
        string $uuidHint = '',
        int $inboundHint = 0
    ): ?array {
        if ($serverType === 'marzban') {
            return self::lookupMarzban($serverId, (string) ($parsed['marzbanText'] ?? $parsed['value']));
        }
        if ($serverType === 'pasarguard') {
            return self::lookupPasarguard($serverId, $parsed, $remarkHint);
        }
        if (isXuiPanel($serverId)) {
            return self::lookupXuiV3($serverId, $parsed, $remarkHint, $uuidHint, $inboundHint);
        }
        return self::lookupLegacyInbound($serverId, $parsed['value']);
    }

    /** @return array<string, mixed>|null */
    private static function lookupMarzban(int $serverId, string $needle): ?array
    {
        global $buttonValues;
        if ($needle === '') {
            return null;
        }
        $usersList = getMarzbanJson($serverId)->users ?? [];
        foreach ($usersList as $config) {
            if (!strstr(json_encode($config->links ?? [], JSON_UNESCAPED_UNICODE), $needle)) {
                continue;
            }
            $leftMb = $config->data_limit != 0 ? $config->data_limit - $config->used_traffic : 'نامحدود';
            if (is_numeric($leftMb)) {
                $leftMb = $leftMb < 0 ? 0 : sumerize($leftMb);
            }
            $expiryDay = $config->expire != 0
                ? floor(($config->expire - time()) / 86400)
                : 'نامحدود';
            if (is_numeric($expiryDay) && $expiryDay < 0) {
                $expiryDay = 0;
            }
            return self::buildResult(
                $serverId,
                true,
                (string) $config->username,
                (string) $needle,
                $config->status === 'active' ? $buttonValues['active'] : $buttonValues['deactive'],
                null,
                null,
                sumerize($config->used_traffic),
                $config->data_limit != 0 ? sumerize($config->data_limit) : 'نامحدود',
                $leftMb,
                $config->expire != 0 ? jdate('Y-m-d H:i:s', $config->expire) : 'نامحدود',
                $expiryDay,
                ['remark' => (string) $config->username, 'uuid' => (string) $needle, 'marzban' => true],
                [
                    'up' => (int) $config->used_traffic,
                    'down' => 0,
                    'total' => (int) $config->data_limit,
                    'left' => is_numeric($leftMb) ? max(0, (int) $config->data_limit - (int) $config->used_traffic) : 0,
                ]
            );
        }
        return null;
    }

    /**
     * @param array{type:string,value:string,marzbanText?:string} $parsed
     * @return array<string, mixed>|null
     */
    private static function lookupPasarguard(int $serverId, array $parsed, string $remarkHint = ''): ?array
    {
        global $buttonValues;
        $remark = $remarkHint;
        if ($remark === '' && $parsed['type'] === 'token') {
            $remark = $parsed['value'];
        }
        if ($remark === '' && ($parsed['type'] ?? '') === 'subId') {
            $stmt = $GLOBALS['connection']->prepare(
                'SELECT `remark` FROM `orders_list` WHERE `token` = ? OR `uuid` = ? LIMIT 1'
            );
            $sub = $parsed['value'];
            $stmt->bind_param('ss', $sub, $sub);
            $stmt->execute();
            $row = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            $remark = (string) ($row['remark'] ?? '');
        }
        if ($remark === '') {
            return null;
        }

        $config = getPasarguardUser($serverId, $remark);
        if (!is_object($config) || empty($config->username)) {
            return null;
        }

        $leftMb = $config->data_limit != 0 ? $config->data_limit - $config->used_traffic : 'نامحدود';
        if (is_numeric($leftMb)) {
            $leftMb = $leftMb < 0 ? 0 : sumerize($leftMb);
        }
        $expiryDay = $config->expire != 0
            ? floor(((int) $config->expire - time()) / 86400)
            : 'نامحدود';
        if (is_numeric($expiryDay) && $expiryDay < 0) {
            $expiryDay = 0;
        }

        return self::buildResult(
            $serverId,
            true,
            (string) $config->username,
            (string) ($parsed['value'] ?? $config->username),
            $config->status === 'active' ? $buttonValues['active'] : $buttonValues['deactive'],
            null,
            null,
            sumerize($config->used_traffic),
            $config->data_limit != 0 ? sumerize($config->data_limit) : 'نامحدود',
            $leftMb,
            $config->expire != 0 ? jdate('Y-m-d H:i:s', (int) $config->expire) : 'نامحدود',
            $expiryDay,
            ['remark' => (string) $config->username, 'uuid' => (string) ($parsed['value'] ?? ''), 'pasarguard' => true],
            [
                'up' => (int) $config->used_traffic,
                'down' => 0,
                'total' => (int) $config->data_limit,
                'left' => is_numeric($leftMb) ? max(0, (int) $config->data_limit - (int) $config->used_traffic) : 0,
            ]
        );
    }

    /**
     * @param array{type:string,value:string} $parsed
     * @return array<string, mixed>|null
     */
    private static function lookupXuiV3(
        int $serverId,
        array $parsed,
        string $remarkHint,
        string $uuidHint,
        int $inboundHint
    ): ?array {
        $service = getXuiV3Service($serverId);
        $resolved = null;

        if ($parsed['type'] === 'subId') {
            $resolved = $service->findClientBySubId($parsed['value']);
        } elseif ($parsed['type'] === 'uuid') {
            $resolved = $service->findClientByUuid($parsed['value']);
        } elseif ($parsed['type'] === 'token' && $remarkHint !== '') {
            $resolved = $service->resolveClientByEmail($remarkHint);
        }

        if (!$resolved && $remarkHint !== '') {
            $resolved = $service->resolveClientByEmail($remarkHint);
        }
        if (!$resolved && $uuidHint !== '') {
            $resolved = $service->findClientByUuid($uuidHint);
        }
        if (!$resolved) {
            return null;
        }

        return self::buildResultFromXuiClient($serverId, $resolved, $inboundHint);
    }

    /** @param array<string, mixed> $resolved */
    private static function buildResultFromXuiClient(int $serverId, array $resolved, int $inboundHint = 0): array
    {
        global $buttonValues;
        $client = $resolved['client'];
        $email = (string) $resolved['email'];
        $uuid = (string) ($resolved['uuid'] ?? '');
        $inboundId = $inboundHint > 0
            ? $inboundHint
            : (int) (($resolved['inbound_ids'][0] ?? 0));

        $up = (int) ($resolved['up'] ?? 0);
        $down = (int) ($resolved['down'] ?? 0);
        $totalBytes = (int) ($resolved['total_bytes'] ?? 0);
        $expiryMs = (int) ($resolved['expiry_ms'] ?? 0);
        $enabled = !empty($resolved['enable']);

        $leftRaw = $totalBytes > 0 ? max(0, $totalBytes - $up - $down) : 'نامحدود';
        $leftMb = is_numeric($leftRaw) ? sumerize($leftRaw) : $leftRaw;
        $expirySec = $expiryMs > 0 ? (int) floor($expiryMs / 1000) : 0;
        $expiryDay = $expirySec > 0 ? floor(($expirySec - time()) / 86400) : 'نامحدود';
        if (is_numeric($expiryDay) && $expiryDay < 0) {
            $expiryDay = 0;
        }

        $configLocation = [
            'uuid' => $uuid,
            'remark' => $email,
        ];
        if ($inboundId > 0) {
            $configLocation['id'] = $inboundId;
        }

        return self::buildResult(
            $serverId,
            false,
            $email,
            $uuid,
            $enabled ? $buttonValues['active'] : $buttonValues['deactive'],
            sumerize($up),
            sumerize($down),
            sumerize($up + $down),
            $totalBytes > 0 ? sumerize($totalBytes) : 'نامحدود',
            $leftMb,
            $expirySec > 0 ? jdate('Y-m-d H:i:s', $expirySec) : 'نامحدود',
            $expiryDay,
            $configLocation,
            [
                'up' => $up,
                'down' => $down,
                'total' => $totalBytes,
                'left' => is_numeric($leftRaw) ? (int) $leftRaw : 0,
            ]
        );
    }

    /** @return array<string, mixed>|null */
    private static function lookupLegacyInbound(int $serverId, string $uuid): ?array
    {
        global $buttonValues;
        $response = getJson($serverId);
        if (!$response->success || !is_array($response->obj)) {
            return null;
        }
        if (!strstr(json_encode($response->obj), $uuid)) {
            return null;
        }

        $list = $response->obj;
        $remark = '';
        $upload = $download = $totalUsed = $total = $leftMb = $expiryTime = $expiryDay = null;
        $state = $buttonValues['deactive'];
        $configLocation = ['uuid' => $uuid, 'remark' => ''];

        if (!isset($list[0]->clientStats)) {
            foreach ($list as $packageInfo) {
                if (!strstr((string) $packageInfo->settings, $uuid)) {
                    continue;
                }
                $configLocation = ['remark' => (string) $packageInfo->remark, 'uuid' => $uuid];
                $remark = (string) $packageInfo->remark;
                $upload = sumerize($packageInfo->up);
                $download = sumerize($packageInfo->down);
                $state = $packageInfo->enable ? $buttonValues['active'] : $buttonValues['deactive'];
                $totalUsed = sumerize($packageInfo->up + $packageInfo->down);
                $total = $packageInfo->total != 0 ? sumerize($packageInfo->total) : 'نامحدود';
                $leftMb = $packageInfo->total != 0
                    ? sumerize(max(0, $packageInfo->total - $packageInfo->up - $packageInfo->down))
                    : 'نامحدود';
                $expiryTime = self::formatLegacyExpiry($packageInfo->expiryTime ?? 0);
                $expiryDay = self::legacyExpiryDays($packageInfo->expiryTime ?? 0);
                break;
            }
        } else {
            $settings = array_column($list, 'settings');
            $keys = -1;
            foreach ($settings as $key => $value) {
                if (strstr((string) $value, $uuid)) {
                    $keys = $key;
                    break;
                }
            }
            if ($keys === -1) {
                return null;
            }
            $clientsSettings = json_decode($list[$keys]->settings, true)['clients'] ?? [];
            if (!is_array($clientsSettings)) {
                return null;
            }
            $settingKey = array_search($uuid, array_column($clientsSettings, 'id'), true);
            if ($settingKey === false) {
                $settingKey = array_search($uuid, array_column($clientsSettings, 'password'), true);
            }
            if ($settingKey === false || !isset($clientsSettings[$settingKey])) {
                return null;
            }
            $clientRow = $clientsSettings[$settingKey];
            $email = (string) ($clientRow['email'] ?? '');
            $clientState = $list[$keys]->clientStats ?? [];
            $emails = array_column($clientState, 'email');
            $emailKey = array_search($email, $emails, true);
            $configLocation = ['id' => (int) $list[$keys]->id, 'remark' => $email ?: (string) $list[$keys]->remark, 'uuid' => $uuid];
            $remark = $email ?: (string) $list[$keys]->remark;

            if ($emailKey !== false && isset($clientState[$emailKey])) {
                $cs = $clientState[$emailKey];
                $totalBytes = (int) ($cs->total ?: $list[$keys]->total);
                $up = (int) $cs->up;
                $down = (int) $cs->down;
                $exp = (int) ($cs->expiryTime ?: $list[$keys]->expiryTime);
                $upload = sumerize($up);
                $download = sumerize($down);
                $totalUsed = sumerize($up + $down);
                $total = $totalBytes != 0 ? sumerize($totalBytes) : 'نامحدود';
                $leftMb = $totalBytes != 0 ? sumerize(max(0, $totalBytes - $up - $down)) : 'نامحدود';
                $expiryTime = self::formatLegacyExpiry($exp);
                $expiryDay = self::legacyExpiryDays($exp);
                $state = $cs->enable ? $buttonValues['active'] : $buttonValues['deactive'];
            }
        }

        if ($remark === '') {
            return null;
        }

        return self::buildResult(
            $serverId,
            false,
            $remark,
            $uuid,
            $state,
            $upload,
            $download,
            $totalUsed,
            $total,
            $leftMb,
            $expiryTime,
            $expiryDay,
            $configLocation
        );
    }

    private static function formatLegacyExpiry($expiryTime): string
    {
        if ((int) $expiryTime === 0) {
            return 'نامحدود';
        }
        $raw = (string) $expiryTime;
        $seconds = strlen($raw) > 11 ? (int) substr($raw, 0, -3) : (int) floor(((int) $raw) / 1000);
        return jdate('Y-m-d H:i:s', $seconds);
    }

    /** @return int|string */
    private static function legacyExpiryDays($expiryTime)
    {
        if ((int) $expiryTime === 0) {
            return 'نامحدود';
        }
        $raw = (string) $expiryTime;
        $seconds = strlen($raw) > 11 ? (int) substr($raw, 0, -3) : (int) floor(((int) $raw) / 1000);
        $days = floor(($seconds - time()) / 86400);
        return $days < 0 ? 0 : $days;
    }

    /**
     * @param array<string, mixed> $configLocation
     * @return array<string, mixed>
     */
    private static function buildResult(
        int $serverId,
        bool $isMarzban,
        string $remark,
        string $uuid,
        string $state,
        $upload,
        $download,
        $totalUsed,
        $total,
        $leftMb,
        $expiryTime,
        $expiryDay,
        array $configLocation,
        ?array $raw = null
    ): array {
        return [
            'found' => true,
            'serverId' => $serverId,
            'isMarzban' => $isMarzban,
            'remark' => $remark,
            'uuid' => $uuid,
            'state' => $state,
            'upload' => $upload,
            'download' => $download,
            'totalUsed' => $totalUsed,
            'total' => $total,
            'leftMb' => $leftMb,
            'expiryTime' => $expiryTime,
            'expiryDay' => $expiryDay,
            'configLocation' => $configLocation,
            'raw' => $raw ?? [],
        ];
    }

    /** @param array<string, mixed> $result */
    public static function exportWebStats(array $result): void
    {
        global $remark, $isMarzban, $totalUsed, $state, $upload, $download, $total, $leftMb, $expiryTime, $expiryDay;
        $raw = $result['raw'] ?? [];
        $isMarzban = !empty($result['isMarzban']);
        $remark = (string) ($result['remark'] ?? '');
        $state = (string) ($result['state'] ?? '');
        $expiryTime = (string) ($result['expiryTime'] ?? '');
        $expiryDay = $result['expiryDay'] ?? 'نامحدود';

        $totalBytes = (int) ($raw['total'] ?? 0);
        $upBytes = (int) ($raw['up'] ?? 0);
        $downBytes = (int) ($raw['down'] ?? 0);
        $leftBytes = (int) ($raw['left'] ?? 0);

        if ($totalBytes <= 0) {
            $total = 'نامحدود';
            $upload = 0;
            $download = 0;
            $totalUsed = sumerize2($upBytes + $downBytes);
            $leftMb = 'نامحدود';
            return;
        }

        $upload = sumerize2($upBytes);
        $download = sumerize2($downBytes);
        $totalUsed = sumerize2($upBytes + $downBytes);
        $total = sumerize2($totalBytes);
        $leftMb = sumerize2(max(0, $leftBytes));
    }

    /** @param array<string, mixed> $result */
    public static function buildTelegramKeyboard(array $result): string
    {
        global $botState, $buttonValues;
        $isMarzban = !empty($result['isMarzban']);
        $serverId = (int) ($result['serverId'] ?? 0);

        $rows = [
            [
                ['text' => (string) ($result['state'] ?? ' '), 'callback_data' => 'wizwizch'],
                ['text' => '🔘 وضعیت اکانت 🔘', 'callback_data' => 'wizwizch'],
            ],
            [
                ['text' => (string) ($result['remark'] ?? ' '), 'callback_data' => 'wizwizch'],
                ['text' => '« نام اکانت »', 'callback_data' => 'wizwizch'],
            ],
        ];

        if (!$isMarzban) {
            $rows[] = [
                ['text' => (string) ($result['upload'] ?? ' '), 'callback_data' => 'wizwizch'],
                ['text' => '√ آپلود √', 'callback_data' => 'wizwizch'],
            ];
            $rows[] = [
                ['text' => (string) ($result['download'] ?? ' '), 'callback_data' => 'wizwizch'],
                ['text' => '√ دانلود √', 'callback_data' => 'wizwizch'],
            ];
        } else {
            $rows[] = [
                ['text' => (string) ($result['totalUsed'] ?? ' '), 'callback_data' => 'wizwizch'],
                ['text' => '√ آپلود + دانلود √', 'callback_data' => 'wizwizch'],
            ];
        }

        $rows[] = [
            ['text' => (string) ($result['total'] ?? ' '), 'callback_data' => 'wizwizch'],
            ['text' => '† حجم کلی †', 'callback_data' => 'wizwizch'],
        ];
        $rows[] = [
            ['text' => (string) ($result['leftMb'] ?? ' '), 'callback_data' => 'wizwizch'],
            ['text' => '~ حجم باقیمانده ~', 'callback_data' => 'wizwizch'],
        ];
        $rows[] = [
            ['text' => (string) ($result['expiryTime'] ?? ' '), 'callback_data' => 'wizwizch'],
            ['text' => 'تاریخ اتمام', 'callback_data' => 'wizwizch'],
        ];
        $rows[] = [
            ['text' => (string) ($result['expiryDay'] ?? ' '), 'callback_data' => 'wizwizch'],
            ['text' => 'تعداد روز باقیمانده', 'callback_data' => 'wizwizch'],
        ];

        if ($botState['renewAccountState'] === 'on' && $botState['updateConfigLinkState'] === 'on') {
            $rows[] = [
                ['text' => $buttonValues['renew_config'], 'callback_data' => 'sConfigRenew' . $serverId],
                ['text' => $buttonValues['update_config_connection'], 'callback_data' => 'sConfigUpdate' . $serverId],
            ];
        } elseif ($botState['renewAccountState'] !== 'on' && $botState['updateConfigLinkState'] === 'on') {
            $rows[] = [
                ['text' => $buttonValues['update_config_connection'], 'callback_data' => 'sConfigUpdate' . $serverId],
            ];
        } elseif ($botState['renewAccountState'] === 'on' && $botState['updateConfigLinkState'] !== 'on') {
            $rows[] = [
                ['text' => $buttonValues['renew_config'], 'callback_data' => 'sConfigRenew' . $serverId],
            ];
        }

        $rows[] = [['text' => 'صفحه اصلی', 'callback_data' => 'mainMenu']];
        return json_encode(['inline_keyboard' => $rows]);
    }
}
