File size: 25,772 Bytes
89ec743
00bae55
89ec743
 
 
 
 
254de8b
 
491dc23
89ec743
51dd86d
 
 
 
 
 
 
 
 
9c944bc
4961765
 
 
51dd86d
abbb25c
51dd86d
187ad5e
 
 
51dd86d
 
 
 
 
89ec743
 
 
 
 
 
 
 
8202ff6
254de8b
 
 
 
89ec743
4961765
 
 
 
 
 
404ae9f
 
 
89ec743
348e845
 
 
 
 
 
 
 
 
 
 
 
89ec743
 
bc118c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89ec743
0b44487
89ec743
 
 
 
 
 
 
 
 
 
 
 
9c944bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e47e6a
 
bfc5154
 
 
 
 
0b44487
0b5ed19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89ec743
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fff265a
 
 
 
 
89ec743
 
 
 
 
 
 
 
d22e23c
 
 
89ec743
 
befdc5c
 
 
89ec743
 
 
 
062cc32
41e9bf8
 
 
 
062cc32
41e9bf8
 
89ec743
 
41e9bf8
 
 
 
 
 
 
 
 
89ec743
 
41e9bf8
062cc32
 
 
 
 
 
 
7cd47d0
41e9bf8
 
062cc32
 
7cd47d0
062cc32
89ec743
 
 
 
 
a9e048b
062cc32
a9e048b
 
 
062cc32
 
a9e048b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
852aacf
a9e048b
852aacf
a9e048b
 
 
 
 
062cc32
 
 
 
 
 
9827bc0
 
 
062cc32
9827bc0
 
062cc32
 
 
9827bc0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
062cc32
9827bc0
 
062cc32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89ec743
b8c2ad3
8d9e872
 
 
 
 
 
b8c2ad3
 
03cf3ae
b8c2ad3
1a82561
b207544
b8c2ad3
 
b79e233
 
 
 
 
 
 
 
 
89ec743
 
 
 
b8c2ad3
 
 
223d57e
 
 
 
89ec743
223d57e
 
 
 
 
 
 
 
 
 
 
89ec743
223d57e
 
 
89ec743
223d57e
 
 
 
 
 
 
 
89ec743
223d57e
 
 
 
a680109
223d57e
b38e96a
223d57e
 
b38e96a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223d57e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b39d407
223d57e
b39d407
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223d57e
 
 
b39d407
223d57e
 
 
b39d407
 
223d57e
b39d407
 
 
 
 
 
223d57e
 
 
 
06e2bc3
 
223d57e
06e2bc3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223d57e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3850b7
 
 
223d57e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4499de2
223d57e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87f52e3
6fbf164
223d57e
 
6fbf164
87f52e3
 
 
6fbf164
87f52e3
6fbf164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223d57e
6fbf164
 
223d57e
6fbf164
 
87f52e3
 
 
 
 
 
0012e69
6fbf164
 
edea4a9
 
 
 
223d57e
 
 
 
 
 
 
 
 
 
89ec743
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
const { Telegraf, session, Scenes, Markup } = require('telegraf');
console.log("🚀 Bot Version 2.0: FINAL DEPLOY (Cache Bust)");
const config = require('./config');
const connectDB = require('./database/db');
const userController = require('./controllers/userController');
const User = require('./models/User');
const Order = require('./models/Order');
const https = require('https');
const dns = require('dns');
const path = require('path');

// --- API & Express Integration ---
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const apiRoutes = require('./api/routes');

const app = express();
app.use(cors());
app.use(bodyParser.json());
// Inject Bot into Request
// Inject Bot into Request (Moved below)
// const bot = require('../main'); // CIRCULAR DEPENDENCY & ERROR
// app.use((req, res, next) => { ... });
app.use('/api', apiRoutes);
app.use('/public', express.static(path.join(__dirname, '../public'))); // Serve Static Files (APK, etc.)

app.get('/', (req, res) => res.send('Bot & API are running!')); // Health Check for Hugging Face

const PORT = process.env.PORT || 7860; // Use 7860 for HF Spaces
app.listen(PORT, () => {
    console.log(`🌍 API Server running on port ${PORT}`);
});
// --------------------------------

if (!config.BOT_TOKEN) {
    console.error('BOT_TOKEN is missing in .env');
    process.exit(1);
}

// Connect to Database
connectDB();

const telegramAgent = require('./utils/telegramAgent');

const bot = new Telegraf(config.BOT_TOKEN, {
    telegram: { agent: telegramAgent }
});

// Inject Bot into Request (Fixed)
app.use((req, res, next) => {
    req.bot = bot;
    next();
});

module.exports = bot; // Export bot instance


// Middleware
// Middleware: Check Block Status
const checkUserStatus = async (ctx, next) => {
    if (ctx.from) {
        const user = await User.findOne({ id: ctx.from.id });
        if (user && user.isBlocked) {
            return ctx.reply("🚫 <b>Siz bloklangansiz.</b>\n\nAdmin bilan bog'laning.", { parse_mode: 'HTML' });
        }
    }
    return next();
};

bot.use(checkUserStatus);
bot.use(session());

// Global i18n & User Middleware
bot.use(async (ctx, next) => {
    try {
        if (ctx.from) {
            const User = require('./models/User');
            const locales = require('./locales');
            // Optimistic check: maybe session has language? (For speed)
            // But for correctness, let's fetch DB or rely on cached user if possible.
            // For now, fast fetch.
            const user = await User.findOne({ id: ctx.from.id });
            const lang = (user && user.language) ? user.language : 'uz';
            ctx.i18n = locales[lang] || locales.uz;
            ctx.dbUser = user;
        }
    } catch (e) {
        console.error("Middleware Error:", e);
        const locales = require('./locales');
        ctx.i18n = locales.uz; // Fallback
    }
    return next();
});

// Scenes
const stage = require('./scenes/index');
bot.use(stage.middleware());

const adminController = require('./controllers/adminController');

// Admin Commands
bot.command('admin', (ctx) => {
    if (!config.ADMIN_IDS.includes(ctx.from.id.toString())) return;
    adminController.showDashboard(ctx);
});

// Admin Callbacks
bot.action('admin_dashboard', (ctx) => adminController.showDashboard(ctx));
// Admin Callbacks
bot.action('admin_dashboard', (ctx) => adminController.showDashboard(ctx));
bot.action('admin_api', (ctx) => adminController.showApiMenu(ctx));

// --- CONTACT HANDLER (For Phone Login) ---
bot.on('contact', async (ctx) => {
    try {
        const contact = ctx.message.contact;
        // Check if contact belongs to the user
        if (contact.user_id !== ctx.from.id) {
            return ctx.reply("🔒 Iltimos, o'zingizning raqamingizni yuboring (tugmani bosing).");
        }

        let user = await User.findOne({ id: ctx.from.id });
        if (!user) {
            user = new User({
                id: ctx.from.id,
                first_name: ctx.from.first_name,
                username: ctx.from.username,
                phone: contact.phone_number.replace('+', '')
            });
            await user.save();
        } else {
            user.phone = contact.phone_number.replace('+', '');
            await user.save();
        }

        ctx.reply(`✅ <b>Raqam tasdiqlandi!</b>\n\nEndi ilovaga kirib, <b>${user.phone}</b> raqamini kiritsangiz, men sizga kod yuboraman.`, { parse_mode: 'HTML' });
    } catch (e) {
        console.error("Contact Error:", e);
        ctx.reply("Xatolik yuz berdi.");
    }
});
bot.action('admin_api_generate', (ctx) => adminController.generateApiKey(ctx)); // NEW

bot.action('admin_users', (ctx) => adminController.showUsers(ctx));
bot.action(/admin_user_manage_(.+)/, (ctx) => adminController.manageUser(ctx, ctx.match[1]));
bot.action(/user_block_(.+)/, (ctx) => adminController.toggleBlockUser(ctx, ctx.match[1], true));
bot.action(/user_unblock_(.+)/, (ctx) => adminController.toggleBlockUser(ctx, ctx.match[1], false));

bot.action('admin_flash_sale', (ctx) => ctx.scene.enter('admin_flash_sale'));
bot.action('admin_inventory', (ctx) => adminController.startMassPriceUpdate(ctx)); // Reuse Inventory button for Mass Update temporarily or create new scene

// Mass Update Text Handler
bot.on('text', async (ctx, next) => {
    // Check for Mass Update State
    if (ctx.session && ctx.session.isMassUpdating) {
        const handled = await adminController.handleMassUpdate(ctx);
        if (handled) return;
    }
    // Check for Name Change State
    if (ctx.session && ctx.session.isChangingName) {
        const handled = await userController.handleNameChange(ctx);
        if (handled) return;
    }
    // Check for Search State
    if (ctx.session && ctx.session.isSearching) {
        const handled = await userController.handleSearch(ctx);
        if (handled) return;
    }

    // Default flow
    return next();
});
bot.action('admin_delete_product', (ctx) => adminController.showDeleteProductList(ctx)); // Ensure this exists
bot.action('admin_edit_product_list', (ctx) => adminController.showEditProductList(ctx));
bot.action('admin_excel_export', (ctx) => adminController.exportOrders(ctx));
bot.action('admin_stats', (ctx) => adminController.showStats(ctx));
bot.action('admin_orders_new', (ctx) => adminController.showNewOrders(ctx));

bot.action('admin_add_product', (ctx) => {
    ctx.deleteMessage(); // Remove dashboard to clear screen
    ctx.scene.enter('ADD_PRODUCT_SCENE');
});

bot.action('admin_broadcast', (ctx) => {
    ctx.deleteMessage();
    ctx.scene.enter('BROADCAST_SCENE');
});

bot.action('admin_settings', (ctx) => {
    ctx.deleteMessage();
    ctx.scene.enter('SETTINGS_SCENE');
});

bot.action(/edit_prod_(.+)/, (ctx) => {
    const prodId = parseInt(ctx.match[1]);
    ctx.scene.enter('EDIT_PRODUCT_SCENE', { prodId: prodId });
});

bot.action(/admin_order_(.+)/, (ctx) => adminController.viewOrder(ctx, ctx.match[1]));
bot.action(/order_accept_(.+)/, (ctx) => adminController.acceptOrder(ctx, ctx.match[1]));
bot.action(/order_reject_(.+)/, (ctx) => adminController.rejectOrder(ctx, ctx.match[1]));
bot.action(/order_ship_(.+)/, (ctx) => adminController.shipOrder(ctx, ctx.match[1])); // NEW
bot.action(/order_deliver_(.+)/, (ctx) => adminController.deliverOrder(ctx, ctx.match[1])); // NEW
bot.action(/gen_invoice_(.+)/, (ctx) => adminController.showInvoice(ctx, ctx.match[1]));
bot.action(/delete_prod_(.+)/, (ctx) => adminController.deleteProduct(ctx, ctx.match[1]));

// User Actions
bot.action(/user_cancel_order_(.+)/, (ctx) => userController.cancelUserOrder(ctx, ctx.match[1]));

// User Commands
bot.start(async (ctx) => {
    try {
        const user = ctx.from;
        let dbUser = await User.findOne({ id: user.id });

        // Handle Referral
        const payload = ctx.startPayload;

        if (!dbUser) {
            // NEW USER: Create with null language to force selection
            dbUser = new User({
                id: user.id,
                first_name: user.first_name,
                username: user.username,
                language: null // Explicitly null
            });
            await dbUser.save();

            // If referral exists, handle it
            if (payload && payload !== user.id.toString()) {
                userController.handleReferral(ctx, payload);
            }
        }

        // FORCE LANGUAGE SELECTION IF NULL
        if (!dbUser.language) {
            return ctx.reply("🇺🇿 Iltimos, tilni tanlang:\n🇷🇺 Пожалуйста, выберите язык:\n🇬🇧 Please select a language:", Markup.inlineKeyboard([
                [Markup.button.callback("🇺🇿 O'zbekcha", "set_lang_uz")],
                [Markup.button.callback("🇷🇺 Русский", "set_lang_ru")],
                [Markup.button.callback("🇬🇧 English", "set_lang_en")]
            ]));
        }

        // If language exists, proceed to menu
        // Attach locale using the user's language
        const locales = require('./locales');
        ctx.i18n = locales[dbUser.language] || locales.uz;

        userController.start(ctx);
    } catch (err) {
        console.error(err);
    }
});

// Middleware: Global Checks (Blocking, Language, etc.)
bot.use(async (ctx, next) => {
    try {
        if (!ctx.from) return next();

        const User = require('./models/User');
        const locales = require('./locales');

        // 1. Find or Create User (Lightweight check)
        let user = await User.findOne({ id: ctx.from.id });

        // If user completely doesn't exist, we usually handle it in /start, 
        // but for safety in other updates, we might ignore or let /start handle it.
        // However, if we want strict logic, we can create them here or wait for /start.

        // 2. Blocked Check
        if (user && user.isBlocked) {
            return ctx.reply("🚫 <b>Siz bloklangansiz.</b>\n\nAdmin bilan bog'laning.", { parse_mode: 'HTML' });
        }

        // 3. Language Check (The "Maximal Logic")
        // If user exists but has no language, FORCE selection.
        // Exception: logic shouldn't block the CALLBACK events using to SET the language.
        const isLanguageSetAction = ctx.callbackQuery && ['set_lang_uz', 'set_lang_ru', 'set_lang_en'].includes(ctx.callbackQuery.data);
        const isStart = ctx.message && ctx.message.text && ctx.message.text.startsWith('/start');

        if (user && !user.language && !isLanguageSetAction && !isStart) {
            return ctx.reply("🇺🇿 Iltimos, oldin tilni tanlang:\n🇷🇺 Пожалуйста, сначала выберите язык:\n🇬🇧 Please select a language first:", Markup.inlineKeyboard([
                [Markup.button.callback("🇺🇿 O'zbekcha", "set_lang_uz")],
                [Markup.button.callback("🇷🇺 Русский", "set_lang_ru")],
                [Markup.button.callback("🇬🇧 English", "set_lang_en")]
            ]));
        }

        // 4. Attach i18n
        // If user has language, use it. If not, default to 'uz' (but /start will force selection)
        const lang = (user && user.language) ? user.language : 'uz';
        ctx.i18n = locales[lang] || locales.uz;

        return next();
    } catch (err) {
        console.error("Middleware Error:", err);
        return next();
    }
});

// Language Selection Handlers
const setLanguage = async (ctx, lang) => {
    try {
        await ctx.answerCbQuery(); // Stop loading animation

        // 1. Update Database
        await User.updateOne({ id: ctx.from.id }, { language: lang });

        // 2. Update Context Locale Immediately
        const locales = require('./locales');
        ctx.i18n = locales[lang];

        // 3. Clean up previous valid/invalid messages
        try {
            await ctx.deleteMessage();
        } catch (e) {
            // Message might be too old or already deleted
        }

        // 4. Send Confirmation in NEW Language
        await ctx.reply(`✅ ${ctx.i18n.language_set}`, { parse_mode: 'HTML' });

        // 5. Refresh Main Menu with NEW Language Buttons
        // We pass a flag or text to start to indicate a refresh, 
        // but userController.start(ctx) usually handles simple menu generation.
        // Let's ensure it sends the FULL welcome text again or just the menu.
        // For "Maximal Logic", resetting to the main menu with a fresh "Welcome back" or just the menu is best.

        userController.start(ctx);

    } catch (e) {
        console.error("Language Change Error:", e);
        ctx.reply("⚠️ Error changing language. Please try again or /start");
    }
};

bot.action('set_lang_uz', (ctx) => setLanguage(ctx, 'uz'));
bot.action('set_lang_ru', (ctx) => setLanguage(ctx, 'ru'));
bot.action('set_lang_en', (ctx) => setLanguage(ctx, 'en'));

// Settings: Change Language Action
bot.action('change_lang', (ctx) => {
    ctx.reply("🇺🇿 Iltimos, tilni tanlang:\n🇷🇺 Пожалуйста, выберите язык:\n🇬🇧 Please select a language:", Markup.inlineKeyboard([
        [Markup.button.callback("🇺🇿 O'zbekcha", "set_lang_uz")],
        [Markup.button.callback("🇷🇺 Русский", "set_lang_ru")],
        [Markup.button.callback("🇬🇧 English", "set_lang_en")]
    ]));
});


// User Interactions
// User Interactions
// Match Helper to get keywords from all languages
const match = (key) => {
    const locales = require('./locales');
    const triggers = Object.values(locales).map(l => l[key]).filter(v => v); // Filter out undefined/null
    return triggers.length > 0 ? triggers : [new RegExp(`^${key}$`)]; // Fallback if empty (shouldn't happen)
};

bot.hears(match('btn_catalog'), (ctx) => userController.showCategories(ctx));
// Syntax Fix Applied
bot.hears(match('btn_cart'), (ctx) => userController.showCart(ctx));
bot.hears(match('btn_orders'), (ctx) => userController.showOrderHistory(ctx)); // Fixed Name
bot.hears(match('btn_contact'), (ctx) => userController.showContact(ctx)); // Use Controller
bot.hears(match('btn_search'), (ctx) => ctx.reply(ctx.i18n.search_prompt));

// Language Switch Handler
bot.hears(match('btn_lang'), (ctx) => {
    ctx.reply("🇺🇿 Iltimos, tilni tanlang:\n🇷🇺 Пожалуйста, выберите язык:\n🇬🇧 Please select a language:", Markup.inlineKeyboard([
        [Markup.button.callback("🇺🇿 O'zbekcha", "set_lang_uz")],
        [Markup.button.callback("🇷🇺 Русский", "set_lang_ru")],
        [Markup.button.callback("🇬🇧 English", "set_lang_en")]
    ]));
});

bot.hears("👨‍💼 Admin Panel", (ctx) => {
    if (!config.ADMIN_IDS.includes(ctx.from.id.toString())) return;
    adminController.showDashboard(ctx);
});

bot.hears(match('btn_contact'), (ctx) => {
    ctx.reply(ctx.i18n.contact_info, Markup.inlineKeyboard([
        [Markup.button.url("📦 Mahsulotlar bo'yicha Admin", "https://t.me/isfandiyor_3")],
        [Markup.button.url("🤖 Bot bo'yicha Admin", "https://t.me/IBROHM_7")]
    ]));
});

bot.hears(match('btn_channel'), (ctx) => {
    ctx.reply(ctx.i18n.btn_channel, Markup.inlineKeyboard([
        [Markup.button.url("📢 Kanalga o'tish", "https://t.me/Pc_Store_Market")]
    ]));
});

bot.hears(match('btn_group'), (ctx) => {
    ctx.reply(ctx.i18n.btn_group, Markup.inlineKeyboard([
        [Markup.button.url("👥 Guruhga o'tish", "https://t.me/Pc_Store_Market1")]
    ]));
});

bot.hears(match('btn_invite'), (ctx) => userController.inviteFriends(ctx));
bot.hears(match('btn_settings'), (ctx) => userController.settings(ctx));
bot.hears(match('btn_back'), (ctx) => userController.start(ctx));

// Language Change Button Handler
bot.hears(match('btn_lang'), (ctx) => {
    ctx.reply("🇺🇿 Iltimos, tilni tanlang:\n🇷🇺 Пожалуйста, выберите язык:\n🇬🇧 Please select a language:", Markup.inlineKeyboard([
        [Markup.button.callback("🇺🇿 O'zbekcha", "set_lang_uz")],
        [Markup.button.callback("🇷🇺 Русский", "set_lang_ru")],
        [Markup.button.callback("🇬🇧 English", "set_lang_en")]
    ]));
});

// Handle /start with Payload
bot.start((ctx) => {
    // Check for payload
    const payload = ctx.startPayload; // Telegraf extracts this automatically "start 123" -> "123"

    // Register User logic here (or ensure it's done) - Middlewares usually trigger first, ensuring user exists.
    // Register User logic here
    // Pass payload to controller
    if (payload) {
        if (payload.startsWith('login_')) {
            // --- NEW: LOGIN HANDLER ---
            const token = payload.replace('login_', '');
            const LoginRequest = require('./models/LoginRequest');

            // Update the login request with user info
            LoginRequest.findOneAndUpdate(
                { token: token },
                {
                    userId: user.id.toString(),
                    firstName: user.first_name,
                    username: user.username,
                    status: 'approved'
                },
                { upsert: true } // Create if doesn't exist (though App should have created it, or we rely on token uniqueness)
            ).then(() => {
                ctx.reply(`✅ <b>Muvaffaqiyatli kirish!</b>\n\nIlovaga qaytishingiz mumkin.`, { parse_mode: 'HTML' });
            }).catch(err => {
                console.error("Login Error:", err);
                ctx.reply("⚠️ Xatolik yuz berdi. Qaytadan urunib ko'ring.");
            });
            return; // Stop here, don't show welcome menu
        }

        userController.handleReferral(ctx, payload);
    }

    const user = ctx.from;
    const welcomeText = `👋 <b>Assalomu alaykum, ${user.first_name}!</b>\n\n💻 <b>PC Store</b> botiga xush kelibsiz!\nSifati va halolligi kafolatlangan texnikalar makoni.\n\n👇 <i>Quyidagi bo'limlardan birini tanlang:</i>`;
    userController.start(ctx, welcomeText);
});

bot.hears("🔎 Qidiruv", (ctx) => userController.startSearch(ctx));
bot.hears("📦 Buyurtmalarim", (ctx) => userController.showOrderHistory(ctx));
bot.hears("⚙️ Sozlamalar", (ctx) => userController.settings(ctx));

bot.action("set_name", (ctx) => {
    ctx.deleteMessage();
    userController.changeName(ctx);
});


// Handle Category Selection (Expanded for Condition Suffix)
bot.action(/cat_(.+)/, async (ctx) => {
    // Expected format: cat_123 or cat_123_new or cat_123_used
    const fullMatch = ctx.match[1];
    let catId = fullMatch;
    let condition = null;

    if (fullMatch.endsWith('_new')) {
        catId = fullMatch.replace('_new', '');
        condition = 'new';
    } else if (fullMatch.endsWith('_used')) {
        catId = fullMatch.replace('_used', '');
        condition = 'used';
    } else if (fullMatch.endsWith('_all')) {
        catId = fullMatch.replace('_all', '');
        condition = 'all';
    }

    const Category = require('./models/Category');
    const userController = require('./controllers/userController');

    // Check if subcategories exist
    const subCount = await Category.countDocuments({ parent: catId });

    if (subCount > 0) {
        // Show Subcategories (passing condition filter)
        return userController.showSubCategories(ctx, catId, condition);
    } else {
        // Show Products (Leaf Category) -> Skip asking if condition is already set
        if (condition) {
            return userController.browseCategory(ctx, `cat_${catId}`, 0, 0, condition);
        }
        // Fallback for old buttons -> Smart Logic in showProducts
        return userController.showProducts(ctx, `cat_${catId}`);
    }
});

// Carousel Navigation
// Condition Filter Selection
bot.action(/cond_(.+)_(.+)/, (ctx) => {
    const catId = "cat_" + ctx.match[1];
    const condition = ctx.match[2];
    userController.filterByCondition(ctx, catId, condition);
});

// Carousel Navigation (New)
bot.action(/br_(.+)/, (ctx) => {
    // pattern: br_catId_prodIndex_mediaIndex_condition
    // We split manually because condition is optional and captured groups are tricky
    const text = ctx.match[1];
    const parts = text.split('_');

    // Safety check
    if (parts.length < 3) return ctx.answerCbQuery("Error");

    const catId = "cat_" + parts[0];
    const prodIndex = parseInt(parts[1]);
    const mediaIndex = parseInt(parts[2]);
    const condition = parts[3] || 'all'; // Default to all if missing

    userController.browseCategory(ctx, catId, prodIndex, mediaIndex, condition);
});

// Backward compatibility or redirect
bot.action(/browse_(.+)_(.+)/, (ctx) => {
    const catId = "cat_" + ctx.match[1];
    const index = parseInt(ctx.match[2]);
    userController.browseCategory(ctx, catId, index, 0);
});

bot.action(/rate_ask_(.+)/, (ctx) => userController.askRating(ctx, ctx.match[1]));
bot.action(/rate_save_(.+)_(.+)/, (ctx) => userController.saveRating(ctx, ctx.match[1], ctx.match[2]));

bot.action("back_to_cats", (ctx) => {
    ctx.deleteMessage();
    userController.showCategories(ctx);
});

bot.action("noop", (ctx) => ctx.answerCbQuery()); // Do nothing for page counter

bot.action(/prod_(.+)/, (ctx) => {
    ctx.answerCbQuery();
    userController.showProduct(ctx, ctx.match[1]);
});

bot.action(/add_cart_(.+)/, (ctx) => {
    userController.addToCart(ctx, ctx.match[1]);
});

bot.action("clear_cart", (ctx) => userController.clearCart(ctx));
bot.action(/cart_incr_(.+)/, (ctx) => userController.cartAction(ctx, 'incr', ctx.match[1]));
bot.action(/cart_decr_(.+)/, (ctx) => userController.cartAction(ctx, 'decr', ctx.match[1]));
bot.action(/cart_del_(.+)/, (ctx) => userController.cartAction(ctx, 'del', ctx.match[1]));
bot.action(/remove_item_(.+)/, (ctx) => userController.removeItem(ctx, parseInt(ctx.match[1])));

bot.action("checkout", (ctx) => {
    ctx.answerCbQuery();
    ctx.deleteMessage(); // Clear cart message
    ctx.scene.enter('CHECKOUT_SCENE');
});

bot.action("my_orders_list", (ctx) => {
    ctx.deleteMessage();
    userController.showOrderHistory(ctx);
});

bot.action("back_to_menu", (ctx) => {
    ctx.deleteMessage();
    ctx.reply("Asosiy menyu:", Markup.keyboard(userController.getMenuButtons(ctx)).resize());
    // Wait, getMenuButtons is not exported or defined nicely. userController.start(ctx) sends the menu.
    // Let's just call userController.start(ctx);
    userController.start(ctx);
});

bot.action(/my_order_(.+)/, (ctx) => userController.showOrderDetails(ctx, ctx.match[1]));
bot.action(/cancel_order_(.+)/, (ctx) => userController.cancelOrder(ctx, ctx.match[1])); // New Action

// Handle Text (Search & Settings)
// This MUST be the last text handler
bot.on('text', async (ctx, next) => {
    // 1. Check Search
    let isHandled = await userController.handleSearch(ctx);
    if (isHandled) return;

    // 2. Check Name Change
    isHandled = await userController.handleNameChange(ctx);
    if (isHandled) return;

    return next();
});


// Launch with Retry Logic and DNS Check
// const dns = require('dns'); // Already imported at the top

// Launch Strategy (Polling vs Webhook)
// Launch Strategy (Polling vs Webhook)
const launchBot = async () => {
    try {
        const PORT = process.env.PORT || 7860; // Render/HF default
        // Force Webhook if explicitly requested or if on Render/Railway
        const USE_WEBHOOK = process.env.USE_WEBHOOK === 'true' || process.env.RENDER_EXTERNAL_URL || process.env.RAILWAY_PUBLIC_DOMAIN;
        const DOMAIN = process.env.RENDER_EXTERNAL_URL || process.env.RAILWAY_PUBLIC_DOMAIN || process.env.WEBHOOK_DOMAIN;

        if (USE_WEBHOOK && DOMAIN) {
            // WEBHOOK MODE (Production)
            console.log(`🚀 Starting in WEBHOOK mode on port ${PORT}`);
            console.log(`🔗 Domain: ${DOMAIN}`);

            // Delete any existing webhook first (optional, but good for restart)
            // await bot.telegram.deleteWebhook(); 

            await bot.launch({
                webhook: {
                    domain: DOMAIN,
                    port: PORT
                },
                dropPendingUpdates: true
            });
            console.log(`✅ Webhook Active: ${DOMAIN}/telegraf/<token>`);
        } else {
            // POLLING MODE (Local / Dev)
            console.log(`🚀 Starting in POLLING mode`);

            // Clear any stuck webhooks
            await bot.telegram.deleteWebhook({ drop_pending_updates: true });
            console.log('🔄 Webhook tozalandi (Polling uchun)...');

            await bot.launch({
                dropPendingUpdates: true
            });
            console.log('✅ Bot (Polling) ishga tushdi!');
        }
    } catch (err) {
        console.error('❌ Botni ishga tushirishda xatolik:', err.message);

        if (err.description && err.description.includes('Conflict')) {
            console.warn("⚠️ MUAZZAM DIQQAT: Bot allaqachon boshqa joyda ishlamoqda!");
            console.warn("Agar bu server bo'lsa, demak lokal (kompyuterda) botni o'chirish kerak.");
            console.warn("Yechim: Bitta botni to'xtating.");

            // Increase delay significantly to break the loop
            console.log('⏳ Conflict: 30 soniya kuting...');
            setTimeout(launchBot, 30000);
            return;
        }

        // Retry logic for Polling stability
        const jitter = Math.floor(Math.random() * 5000); // 0-5s random delay
        const delay = 10000 + jitter;
        console.log(`⏳ ${delay / 1000} soniyadan keyin qayta urunib ko'ramiz (Jitter: ${jitter}ms)...`);
        setTimeout(launchBot, delay);
    }
};

launchBot();

// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));

module.exports = bot;