<?php

declare(strict_types=1);

use Bot\Domain\Service\AdminService;
use Bot\Domain\Service\ForcedJoinChannelService;
use Bot\Domain\Service\LogService;
use SergiX44\Nutgram\Telegram\Types\Keyboard\InlineKeyboardButton;
use SergiX44\Nutgram\Telegram\Types\Keyboard\InlineKeyboardMarkup;
use SergiX44\Nutgram\Nutgram;

/**
 * دریافت یا ایجاد admin (اگر owner است)
 */
function getOrCreateAdmin(AdminService $adminService, int $telegramId, ?int $ownerTelegramId): ?\Bot\Domain\Entity\Admin
{
    if ($ownerTelegramId !== null && $telegramId === $ownerTelegramId) {
        return $adminService->getOrCreateOwner($telegramId);
    }
    return $adminService->getByTelegramId($telegramId);
}

/**
 * ویرایش پیام با مدیریت خطاها
 * خطاهای رایج را مدیریت می‌کند و خطاهای غیرمنتظره را لاگ می‌کند
 * 
 * @param Nutgram $bot Instance ربات
 * @param string $text متن جدید پیام
 * @param int|string $chatId شناسه چت
 * @param int $messageId شناسه پیام
 * @param InlineKeyboardMarkup|null $replyMarkup کیبورد اینلاین (اختیاری)
 * @param string|null $parseMode حالت پارس (HTML, Markdown, MarkdownV2) - اختیاری
 * @return bool true در صورت موفقیت یا خطای قابل چشم‌پوشی، false هرگز برنمی‌گرداند (یا throw می‌کند)
 * 
 * @see https://core.telegram.org/bots/api#editmessagetext
 * @see https://nutgram.dev/docs/introduction
 */
function safeEditMessageText(
    Nutgram $bot,
    string $text,
    int|string $chatId,
    int $messageId,
    ?InlineKeyboardMarkup $replyMarkup = null,
    ?string $parseMode = null
): bool {
    try {
        // تلاش برای ویرایش پیام
        $bot->editMessageText(
            text: $text,
            chat_id: $chatId,
            message_id: $messageId,
            reply_markup: $replyMarkup,
            parse_mode: $parseMode
        );
        // عملیات موفق بود
        return true;
    } catch (\SergiX44\Nutgram\Telegram\Exceptions\TelegramException $e) {
        $errorMessage = strtolower($e->getMessage());
        $errorCode = $e->getCode();
        
        // خطاهای قابل چشم‌پوشی (طبق مستندات Telegram Bot API)
        // این خطاها نشان‌دهنده مشکل جدی نیستند و می‌توانند نادیده گرفته شوند
        $ignorablePatterns = [
            'message is not modified',           // MESSAGE_NOT_MODIFIED (400)
            'message not found',                 // MESSAGE_ID_INVALID (400)
            'chat not found',                    // CHAT_NOT_FOUND (400)
            'message to edit not found',         // MESSAGE_ID_INVALID (400)
            'message edit time expired',         // MESSAGE_EDIT_TIME_EXPIRED (400)
            "message can't be edited",           // MESSAGE_EDIT_TIME_EXPIRED یا MESSAGE_NOT_MODIFIED (400)
            'message cannot be edited',          // فرمت دیگر
            'bad request: message is not modified', // فرمت کامل خطا
            'bad request: message not found',
            'bad request: chat not found',
            'bad request: message to edit not found',
            'bad request: message edit time expired',
            "bad request: message can't be edited",
            'bad request: message cannot be edited',
        ];
        
        // بررسی کد خطا: 400 معمولاً خطاهای ورودی است
        // اما برخی از آنها قابل چشم‌پوشی هستند
        $is400Error = ($errorCode === 400);
        
        // بررسی متن خطا برای تشخیص خطاهای قابل چشم‌پوشی
        $isIgnorable = false;
        foreach ($ignorablePatterns as $pattern) {
            if (stripos($errorMessage, $pattern) !== false) {
                $isIgnorable = true;
                break;
            }
        }
        
        // اگر خطا قابل چشم‌پوشی است، true برمی‌گردانیم (نه false!)
        // چون این خطاها نشان‌دهنده مشکل جدی نیستند
        if ($isIgnorable || ($is400Error && stripos($errorMessage, 'not modified') !== false)) {
            // لاگ برای دیباگ (اختیاری)
            error_log(sprintf(
                '[safeEditMessageText] خطای قابل چشم‌پوشی: %s (کد: %d) - ChatId: %s, MessageId: %d',
                $e->getMessage(),
                $errorCode,
                $chatId,
                $messageId
            ));
            // برگرداندن true چون خطا قابل چشم‌پوشی است
            return true;
        }
        
        // خطاهای غیرمنتظره - لاگ و throw
        error_log(sprintf(
            '[safeEditMessageText] خطای غیرمنتظره: %s (کد: %d) - ChatId: %s, MessageId: %d',
            $errorMessage,
            $errorCode,
            $chatId,
            $messageId
        ));
        
        // برای خطاهای غیرمنتظره، exception را دوباره throw می‌کنیم
        throw $e;
    } catch (\Throwable $e) {
        // خطاهای غیرمنتظره دیگر (غیر از TelegramException)
        error_log(sprintf(
            '[safeEditMessageText] خطای غیرمنتظره (Throwable): %s - ChatId: %s, MessageId: %d',
            $e->getMessage(),
            $chatId,
            $messageId
        ));
        throw $e;
    }
}

/**
 * پاسخ به callback query با مدیریت خطاها
 * خطای "query is too old" را نادیده می‌گیرد
 */
function safeAnswerCallbackQuery(
    Nutgram $bot,
    ?string $text = null,
    bool $showAlert = false
): bool {
    try {
        $bot->answerCallbackQuery($text, show_alert: $showAlert);
        return true;
    } catch (\Throwable $e) {
        // اگر callback query قدیمی باشد، خطا را نادیده می‌گیریم
        if (str_contains($e->getMessage(), 'query is too old') || 
            str_contains($e->getMessage(), 'query ID is invalid')) {
            return false;
        }
        // برای سایر خطاها، exception را دوباره throw می‌کنیم
        throw $e;
    }
}

/**
 * ایجاد جدول migrations اگر وجود نداشته باشد
 */
function ensureMigrationsTable(PDO $pdo): void
{
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS migrations (
            id INT AUTO_INCREMENT PRIMARY KEY,
            filename VARCHAR(255) NOT NULL UNIQUE,
            executed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
    ");
}

/**
 * دریافت لیست migration های اجرا شده
 * 
 * @return array<string> لیست نام فایل‌های اجرا شده
 */
function getExecutedMigrations(PDO $pdo): array
{
    ensureMigrationsTable($pdo);
    
    $stmt = $pdo->query('SELECT filename FROM migrations ORDER BY executed_at ASC');
    $migrations = [];
    while ($row = $stmt->fetch()) {
        $migrations[] = $row['filename'];
    }
    
    return $migrations;
}

/**
 * ثبت migration اجرا شده
 */
function markMigrationAsExecuted(PDO $pdo, string $filename): void
{
    $stmt = $pdo->prepare('INSERT INTO migrations (filename) VALUES (:filename)');
    $stmt->execute(['filename' => $filename]);
}

/**
 * اجرای فایل migration
 * 
 * @return array{success: bool, message: string, executed: int}
 */
function runMigrationFile(PDO $pdo, string $filePath, string $filename): array
{
    try {
        $content = file_get_contents($filePath);
        if ($content === false) {
            return [
                'success' => false,
                'message' => "خطا در خواندن فایل: {$filename}",
                'executed' => 0,
            ];
        }
        
        // حذف کامنت‌ها
        $content = preg_replace('/--.*$/m', '', $content);
        $content = preg_replace('/\/\*.*?\*\//s', '', $content);
        
        // تقسیم به دستورات جداگانه
        $statements = [];
        $currentStatement = '';
        $lines = explode("\n", $content);
        
        foreach ($lines as $line) {
            // حذف whitespace از ابتدا و انتها
            $line = rtrim($line);
            
            // نادیده گرفتن خطوط خالی
            if (empty($line)) {
                continue;
            }
            
            // اضافه کردن خط به statement فعلی
            $currentStatement .= $line . "\n";
            
            // اگر خط با ; تمام می‌شود، یک statement کامل است
            if (substr($line, -1) === ';') {
                $statement = trim($currentStatement);
                if (!empty($statement)) {
                    $statements[] = $statement;
                }
                $currentStatement = '';
            }
        }
        
        // اگر statement ناقص باقی مانده باشد، آن را هم اضافه می‌کنیم
        $remaining = trim($currentStatement);
        if (!empty($remaining)) {
            $statements[] = $remaining;
        }
        
        // اجرای دستورات
        // توجه: دستورات DDL (مثل ALTER TABLE) در MySQL به صورت خودکار commit می‌شوند
        // بنابراین از transaction استفاده نمی‌کنیم
        $executed = 0;
        
        try {
            $errors = [];
            foreach ($statements as $statement) {
                $statement = trim($statement);
                if (empty($statement)) {
                    continue;
                }
                
                try {
                    $pdo->exec($statement);
                    $executed++;
                } catch (PDOException $e) {
                    // اگر خطا مربوط به foreign key constraint باشد و constraint از قبل وجود داشته باشد، نادیده می‌گیریم
                    if (str_contains($e->getMessage(), 'Duplicate key name') || 
                        str_contains($e->getMessage(), 'already exists') ||
                        str_contains($e->getMessage(), 'Duplicate foreign key constraint')) {
                        // constraint از قبل وجود دارد، نادیده می‌گیریم
                        continue;
                    }
                    // برای سایر خطاها، exception را throw می‌کنیم
                    throw $e;
                }
            }
            
            // ثبت migration (بعد از موفقیت در اجرای دستورات)
            markMigrationAsExecuted($pdo, $filename);
            
            return [
                'success' => true,
                'message' => "Migration {$filename} با موفقیت اجرا شد ({$executed} دستور)",
                'executed' => $executed,
            ];
        } catch (\Throwable $e) {
            // در صورت خطا، migration ثبت نمی‌شود
            throw $e;
        }
    } catch (\Throwable $e) {
        return [
            'success' => false,
            'message' => "خطا در اجرای migration {$filename}: " . $e->getMessage(),
            'executed' => 0,
        ];
    }
}

/**
 * اجرای تمام migration های موجود در پوشه migrates
 * 
 * @return array{success: bool, total: int, executed: int, failed: int, messages: array<string>}
 */
function runAllMigrations(?PDO $pdo = null): array
{
    if ($pdo === null) {
        require_once __DIR__ . '/database-repositories.php';
        $pdo = createDatabaseConnection();
        if ($pdo === null) {
            return [
                'success' => false,
                'total' => 0,
                'executed' => 0,
                'failed' => 0,
                'messages' => ['❌ اتصال به دیتابیس برقرار نشد!'],
            ];
        }
    }
    
    $migratesDir = __DIR__ . '/../../database/migrates';
    if (!is_dir($migratesDir)) {
        return [
            'success' => false,
            'total' => 0,
            'executed' => 0,
            'failed' => 0,
            'messages' => ['❌ پوشه migrates یافت نشد!'],
        ];
    }
    
    // دریافت لیست migration های اجرا شده
    $executedMigrations = getExecutedMigrations($pdo);
    
    // دریافت لیست فایل‌های SQL
    $files = glob($migratesDir . '/*.sql');
    if ($files === false) {
        return [
            'success' => false,
            'total' => 0,
            'executed' => 0,
            'failed' => 0,
            'messages' => ['❌ خطا در خواندن فایل‌های migration!'],
        ];
    }
    
    // مرتب‌سازی فایل‌ها بر اساس نام
    sort($files);
    
    $total = count($files);
    $executed = 0;
    $failed = 0;
    $messages = [];
    
    foreach ($files as $filePath) {
        $filename = basename($filePath);
        
        // بررسی اینکه آیا قبلاً اجرا شده است
        if (in_array($filename, $executedMigrations, true)) {
            $messages[] = "⏭️ {$filename} - قبلاً اجرا شده است";
            continue;
        }
        
        // اجرای migration
        $result = runMigrationFile($pdo, $filePath, $filename);
        
        if ($result['success']) {
            $executed++;
            $messages[] = "✅ {$result['message']}";
        } else {
            $failed++;
            $messages[] = "❌ {$result['message']}";
        }
    }
    
    return [
        'success' => $failed === 0,
        'total' => $total,
        'executed' => $executed,
        'failed' => $failed,
        'messages' => $messages,
    ];
}

/**
 * بررسی عضویت کاربر در کانال‌های اجباری
 * 
 * @param Nutgram $bot Instance ربات
 * @param int $telegramId شناسه تلگرام کاربر
 * @param ForcedJoinChannelService $forcedJoinChannelService سرویس کانال‌های اجباری
 * @param LogService $logService سرویس لاگ
 * @param string|null $forcedJoinChannel کانال اجباری قدیمی (برای سازگاری)
 * @return array{hasJoined: bool, channels: array<string>, channelEntities: array<\Bot\Domain\Entity\ForcedJoinChannel>} نتیجه بررسی و لیست کانال‌های مورد نیاز
 */
function checkForcedJoinChannels(
    Nutgram $bot,
    int $telegramId,
    ForcedJoinChannelService $forcedJoinChannelService,
    LogService $logService,
    ?string $forcedJoinChannel = null
): array {
    $hasJoined = true;
    $requiredChannels = [];
    $channelEntities = [];
    
    try {
        // دریافت کانال‌های فعال از دیتابیس
        $enabledChannels = $forcedJoinChannelService->getEnabledChannels();
        
        if (!empty($enabledChannels)) {
            foreach ($enabledChannels as $channel) {
                $channelUsername = $channel->getChannelUsername();
                $requiredChannels[] = $channelUsername;
                $channelEntities[] = $channel;
                
                try {
                    // بررسی عضویت کاربر در کانال
                    $member = $bot->getChatMember($channelUsername, $telegramId);
                    
                    // بررسی وضعیت عضویت
                    if ($member === null) {
                        $hasJoined = false;
                        $logService->warning('نتیجه getChatMember null است', [
                            'telegram_id' => $telegramId,
                            'channel' => $channelUsername,
                        ], $telegramId);
                    } else {
                        $status = $member->status ?? null;
                        // اگر کاربر عضو نباشد یا اخراج شده باشد
                        if (in_array($status, ['left', 'kicked'], true)) {
                            $hasJoined = false;
                            $logService->debug('کاربر عضو کانال اجباری نیست', [
                                'telegram_id' => $telegramId,
                                'channel' => $channelUsername,
                                'status' => $status,
                            ], $telegramId);
                        }
                    }
                } catch (\SergiX44\Nutgram\Telegram\Exceptions\TelegramException $e) {
                    $errorMessage = strtolower($e->getMessage());
                    $errorCode = $e->getCode();
                    
                    // خطاهای خاص که نشان می‌دهد کاربر عضو نیست
                    $userNotMemberErrors = [
                        'user not found',
                        'chat not found',
                        'user is not a member',
                        'member not found',
                    ];
                    
                    // خطاهای خاص که نشان می‌دهد ربات عضو کانال نیست
                    $botNotMemberErrors = [
                        'bot is not a member',
                        'bot is not a member of the',
                        'bot not found',
                    ];
                    
                    $isUserNotMember = false;
                    foreach ($userNotMemberErrors as $pattern) {
                        if (str_contains($errorMessage, $pattern)) {
                            $isUserNotMember = true;
                            break;
                        }
                    }
                    
                    $isBotNotMember = false;
                    foreach ($botNotMemberErrors as $pattern) {
                        if (str_contains($errorMessage, $pattern)) {
                            $isBotNotMember = true;
                            break;
                        }
                    }
                    
                    if ($isUserNotMember || ($errorCode === 400 && !$isBotNotMember)) {
                        // کاربر عضو نیست
                        $hasJoined = false;
                        $logService->debug('کاربر عضو کانال اجباری نیست (خطای API)', [
                            'telegram_id' => $telegramId,
                            'channel' => $channelUsername,
                            'error' => $e->getMessage(),
                            'code' => $errorCode,
                        ], $telegramId);
                    } elseif ($isBotNotMember) {
                        // ربات عضو کانال نیست - این یک مشکل پیکربندی است
                        $logService->error('ربات عضو کانال اجباری نیست - لطفاً ربات را به کانال اضافه کنید', [
                            'telegram_id' => $telegramId,
                            'channel' => $channelUsername,
                            'error' => $e->getMessage(),
                            'code' => $errorCode,
                        ], $telegramId);
                        // در این حالت، فرض می‌کنیم کاربر عضو نیست (چون نمی‌توانیم بررسی کنیم)
                        $hasJoined = false;
                    } else {
                        // خطای دیگر
                        $logService->error('خطا در بررسی عضویت کانال', [
                            'telegram_id' => $telegramId,
                            'channel' => $channelUsername,
                            'error' => $e->getMessage(),
                            'code' => $errorCode,
                        ], $telegramId);
                        // در صورت خطای غیرمنتظره، فرض می‌کنیم کاربر عضو نیست
                        $hasJoined = false;
                    }
                } catch (\Throwable $e) {
                    $logService->error('خطای غیرمنتظره در بررسی عضویت کانال', [
                        'telegram_id' => $telegramId,
                        'channel' => $channelUsername,
                        'error' => $e->getMessage(),
                    ], $telegramId);
                    // در صورت خطای غیرمنتظره، فرض می‌کنیم کاربر عضو نیست
                    $hasJoined = false;
                }
            }
        } else {
            // Fallback: استفاده از متغیر محیطی برای سازگاری با نسخه قدیمی
            if ($forcedJoinChannel !== null && $forcedJoinChannel !== '') {
                $requiredChannels[] = $forcedJoinChannel;
                
                try {
                    $member = $bot->getChatMember($forcedJoinChannel, $telegramId);
                    if ($member === null || in_array($member->status ?? '', ['left', 'kicked'], true)) {
                        $hasJoined = false;
                    }
                } catch (\SergiX44\Nutgram\Telegram\Exceptions\TelegramException $e) {
                    $errorMessage = strtolower($e->getMessage());
                    $errorCode = $e->getCode();
                    
                    $userNotMemberErrors = [
                        'user not found',
                        'chat not found',
                        'user is not a member',
                        'member not found',
                    ];
                    
                    $botNotMemberErrors = [
                        'bot is not a member',
                        'bot is not a member of the',
                        'bot not found',
                    ];
                    
                    $isUserNotMember = false;
                    foreach ($userNotMemberErrors as $pattern) {
                        if (str_contains($errorMessage, $pattern)) {
                            $isUserNotMember = true;
                            break;
                        }
                    }
                    
                    $isBotNotMember = false;
                    foreach ($botNotMemberErrors as $pattern) {
                        if (str_contains($errorMessage, $pattern)) {
                            $isBotNotMember = true;
                            break;
                        }
                    }
                    
                    if ($isUserNotMember || ($errorCode === 400 && !$isBotNotMember)) {
                        $hasJoined = false;
                    } elseif ($isBotNotMember) {
                        $logService->error('ربات عضو کانال اجباری نیست - لطفاً ربات را به کانال اضافه کنید (fallback)', [
                            'telegram_id' => $telegramId,
                            'channel' => $forcedJoinChannel,
                            'error' => $e->getMessage(),
                            'code' => $errorCode,
                        ], $telegramId);
                        $hasJoined = false;
                    } else {
                        $logService->error('خطا در بررسی عضویت کانال (fallback)', [
                            'telegram_id' => $telegramId,
                            'channel' => $forcedJoinChannel,
                            'error' => $e->getMessage(),
                            'code' => $errorCode,
                        ], $telegramId);
                        $hasJoined = false;
                    }
                } catch (\Throwable $e) {
                    $logService->error('خطای غیرمنتظره در بررسی عضویت کانال (fallback)', [
                        'telegram_id' => $telegramId,
                        'channel' => $forcedJoinChannel,
                        'error' => $e->getMessage(),
                    ], $telegramId);
                    $hasJoined = false;
                }
            }
        }
    } catch (\Throwable $e) {
        $logService->error('خطا در دریافت کانال‌های اجباری', [
            'telegram_id' => $telegramId,
            'error' => $e->getMessage(),
        ], $telegramId);
        // در صورت خطا، فرض می‌کنیم کاربر عضو نیست
        $hasJoined = false;
    }
    
    return [
        'hasJoined' => $hasJoined,
        'channels' => $requiredChannels,
        'channelEntities' => $channelEntities,
    ];
}

