File size: 50,051 Bytes
0789398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b7dc6e
 
 
 
 
 
 
 
 
 
 
 
0789398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c811366
 
 
 
 
 
 
 
 
 
 
0789398
c811366
 
 
 
 
 
 
 
0789398
c811366
 
 
 
 
0789398
c811366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0789398
 
c811366
 
 
 
 
 
 
 
 
 
 
0789398
c811366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51aab33
 
 
 
 
 
 
 
 
 
 
 
c811366
 
51aab33
c811366
 
 
 
 
 
51aab33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c811366
 
 
 
51aab33
 
c811366
 
 
 
 
 
 
0789398
 
 
c811366
0789398
c811366
51aab33
0789398
c811366
 
 
 
0789398
c811366
 
 
51aab33
 
 
 
 
c811366
 
 
 
 
 
0789398
51aab33
 
 
 
 
 
c811366
0789398
c811366
0789398
51aab33
 
 
 
 
 
c811366
51aab33
c811366
 
0789398
51aab33
0789398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b7dc6e
0789398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b7dc6e
0789398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b7dc6e
0789398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
# ================================================================
# URAL AI - MEGA MODULE v1.0
# File: ural_mega_module.py
#
# GỒM TẤT CẢ TÍNH NĂNG CÒN THIẾU — 1 FILE DUY NHẤT
# HOÀN TOÀN FREE, KHÔNG KEY, KHÔNG PHÍ ẨN
# NẰM TRONG GIỚI HẠN HF FREE TIER
#
# CÁCH DÙNG: Thêm vào app.py (sau các import ural khác):
#            import ural_mega_module
#
# CÁC MODULE TRONG FILE NÀY:
#   [1] Tin tức realtime    — RSS feeds, không cần key
#   [2] Giá crypto          — CoinGecko API, free không cần key
#   [3] Giá vàng/USD/VND    — Tự scrape từ web
#   [4] Thời tiết chi tiết  — wttr.in, free không cần key
#   [5] QR Code tạo         — qrcode library (nhẹ, offline)
#   [6] Nhật ký cảm xúc     — SQLite đã có trong app
#   [7] Đổi đơn vị          — Logic thuần, không cần mạng
#   [8] Tra từ điển VN      — Free dictionary API
#   [9] Tóm tắt URL tốt hơn — Jina.ai đã có, hook tốt hơn
#  [10] Câu đố / Trò chơi   — Logic thuần, nâng cấp quiz_game cũ
# ================================================================

import os, sys, re, json, random, time, datetime, requests
from typing import Optional

# ── [FIX] Đường dẫn DB persistent — TRƯỚC ĐÂY dùng "ural_memory.db" (path
# tương đối) → mất sạch dữ liệu mỗi khi HF Space restart/redeploy vì thư mục
# làm việc không persistent. GIỜ ưu tiên /data (persistent storage của HF),
# fallback về thư mục hiện tại hoặc /tmp nếu /data không truy cập được
# (vd: chạy local dev không có /data).
def _journal_db_path() -> str:
    if os.path.exists("/data") and os.path.isdir("/data") and os.access("/data", os.W_OK):
        return "/data/ural_memory.db"
    return "./ural_memory.db" if os.access(".", os.W_OK) else "/tmp/ural_memory.db"

_URAL_DB_PATH = _journal_db_path()


# ================================================================
# MODULE 1: TIN TỨC REALTIME — RSS Feeds
# Không cần key, cực nhẹ, ổn định
# ================================================================

RSS_SOURCES = {
    "vnexpress":  "https://vnexpress.net/rss/tin-moi-nhat.rss",
    "tuoitre":    "https://tuoitre.vn/rss/tin-moi-nhat.rss",
    "thanhnien":  "https://thanhnien.vn/rss/home.rss",
    "dantri":     "https://dantri.com.vn/rss/home.rss",
    "the_gioi":   "https://vnexpress.net/rss/the-gioi.rss",
    "cong_nghe":  "https://vnexpress.net/rss/khoa-hoc-cong-nghe.rss",
    "kinh_doanh": "https://vnexpress.net/rss/kinh-doanh.rss",
    "giai_tri":   "https://vnexpress.net/rss/giai-tri.rss",
    "the_thao":   "https://vnexpress.net/rss/the-thao.rss",
}

NEWS_KEYWORDS = {
    "thế giới":    "the_gioi",
    "quốc tế":     "the_gioi",
    "công nghệ":   "cong_nghe",
    "tech":        "cong_nghe",
    "kinh doanh":  "kinh_doanh",
    "kinh tế":     "kinh_doanh",
    "giải trí":    "giai_tri",
    "showbiz":     "giai_tri",
    "thể thao":    "the_thao",
    "bóng đá":     "the_thao",
}

_news_cache = {}  # {source: (timestamp, articles)}
NEWS_CACHE_TTL = 300  # 5 phút

def get_news(category: str = "", num: int = 5) -> str:
    """Lấy tin tức realtime — ưu tiên brain_memory cache từ RSS-Watcher."""
    import datetime as _dt_gn

    # ── BƯỚC 1: Dùng brain_memory cache từ RSS-Watcher (đã có sẵn!) ──────
    # RSS-Watcher chạy mỗi 15 phút, lưu vào brain_memory[_rss_hot_YYYY-MM-DD]
    # Đây là nguồn dữ liệu đúng, không cần fetch lại
    try:
        today_key = f"_rss_hot_{_dt_gn.date.today()}"
        rss_cached = brain_memory.get(today_key, "")
        if rss_cached and len(rss_cached) > 50:
            lines = [l.strip() for l in rss_cached.strip().split("\n") if l.strip()]
            # Lấy num tin đầu (mỗi tin = 1 dòng "• [SOURCE] title")
            news_lines = [l for l in lines if l.startswith("•")][:num]
            if news_lines:
                cat_label = category if category else "mới nhất"
                ts = _dt_gn.datetime.now().strftime("%H:%M %d/%m")
                result = [f"📰 **Tin tức {cat_label}** ({ts}):\n"]
                for i, line in enumerate(news_lines, 1):
                    # Format: • [VNEXPRESS] Tiêu đề tin → **1. Tiêu đề tin**
                    clean = re.sub(r"^•\s*\[\w+\]\s*", "", line).strip()
                    result.append(f"**{i}. {clean}**")
                    result.append("")
                return "\n".join(result)
    except Exception as _e_cache:
        pass  # Fallback xuống fetch trực tiếp

    # ── BƯỚC 2: Fallback - fetch RSS trực tiếp ───────────────────────────
    source_key = "vnexpress"
    cat_lower = category.lower()
    for kw, src in NEWS_KEYWORDS.items():
        if kw in cat_lower:
            source_key = src
            break

    url = RSS_SOURCES.get(source_key, RSS_SOURCES["vnexpress"])

    cached = _news_cache.get(source_key)
    if cached and (time.time() - cached[0]) < NEWS_CACHE_TTL:
        articles = cached[1]
    else:
        try:
            resp = requests.get(url, timeout=10, headers={
                "User-Agent": "Mozilla/5.0 (compatible; URAL-AI/1.0)"
            })
            resp.encoding = "utf-8"
            raw = resp.text

            # Parse nhiều format RSS khác nhau
            titles = (re.findall(r'<title><!\[CDATA\[(.*?)\]\]></title>', raw, re.DOTALL)
                   or re.findall(r'<title>(.*?)</title>', raw, re.DOTALL))
            descs  = (re.findall(r'<description><!\[CDATA\[(.*?)\]\]></description>', raw, re.DOTALL)
                   or re.findall(r'<description>(.*?)</description>', raw, re.DOTALL))
            links  = re.findall(r'<link>(https?://[^<]+)</link>', raw)

            if len(titles) > 1:
                titles = titles[1:]  # Bỏ title của feed

            articles = []
            for i in range(min(num + 2, len(titles))):
                title = re.sub(r'<[^>]+>', '', titles[i]).strip() if i < len(titles) else ""
                desc  = re.sub(r'<[^>]+>', '', descs[i+1] if i+1 < len(descs) else "").strip()[:200]
                link  = links[i] if i < len(links) else ""
                if title and len(title) > 5:
                    articles.append({"title": title, "desc": desc, "link": link})

            _news_cache[source_key] = (time.time(), articles)
        except Exception as e:
            return f"⚠️ Không lấy được tin tức: {e}"

    if not articles:
        return "⚠️ Chưa có tin tức trong cache. Thử lại sau 1 phút."

    cat_label = category if category else "mới nhất"
    ts = _dt_gn.datetime.now().strftime("%H:%M %d/%m")
    result = [f"📰 **Tin tức {cat_label}** ({ts}):\n"]
    for i, art in enumerate(articles[:num]):
        result.append(f"**{i+1}. {art['title']}**")
        if art['desc']:
            result.append(f"   {art['desc']}...")
        if art['link']:
            result.append(f"   🔗 {art['link']}")
        result.append("")
    return "\n".join(result)


# ================================================================
# MODULE 2: GIÁ CRYPTO — CoinGecko (free, không cần key)
# ================================================================

CRYPTO_IDS = {
    "bitcoin":   "bitcoin",      "btc":       "bitcoin",
    "ethereum":  "ethereum",     "eth":       "ethereum",
    "bnb":       "binancecoin",  "binance":   "binancecoin",
    "xrp":       "ripple",       "ripple":    "ripple",
    "solana":    "solana",       "sol":       "solana",
    "doge":      "dogecoin",     "dogecoin":  "dogecoin",
    "usdt":      "tether",       "tether":    "tether",
    "ada":       "cardano",      "cardano":   "cardano",
    "ton":       "the-open-network",
    "not":       "notcoin",
    "pepe":      "pepe",
}

_crypto_cache = {}  # {coin_id: (ts, data)}
CRYPTO_CACHE_TTL = 60  # 1 phút

def get_crypto_price(coins: list = None) -> str:
    """Giá crypto realtime từ CoinGecko API — miễn phí, không cần key."""
    if not coins:
        coins = ["bitcoin", "ethereum", "binancecoin", "solana", "ripple"]

    coin_ids = []
    coin_names = {}
    for c in coins:
        cid = CRYPTO_IDS.get(c.lower(), c.lower())
        coin_ids.append(cid)
        coin_names[cid] = c.upper()

    ids_str = ",".join(set(coin_ids))
    url = (f"https://api.coingecko.com/api/v3/simple/price"
           f"?ids={ids_str}&vs_currencies=usd,vnd"
           f"&include_24hr_change=true&include_market_cap=false")

    try:
        resp = requests.get(url, timeout=8)
        data = resp.json()

        now = datetime.datetime.now().strftime("%H:%M %d/%m")
        lines = [f"💰 **Giá Crypto** (cập nhật {now}):\n"]

        for cid, info in data.items():
            name  = coin_names.get(cid, cid.upper())
            usd   = info.get("usd", 0)
            vnd   = info.get("vnd", 0)
            chg   = info.get("usd_24h_change", 0) or 0
            arrow = "📈" if chg >= 0 else "📉"
            sign  = "+" if chg >= 0 else ""

            usd_str = f"${usd:,.2f}" if usd < 1 else f"${usd:,.0f}"
            vnd_str = f"{vnd/1_000_000:.1f}M VND" if vnd > 1_000_000 else f"{vnd:,.0f} VND"

            lines.append(f"{arrow} **{name}**: {usd_str} ({vnd_str}) | 24h: {sign}{chg:.1f}%")

        return "\n".join(lines)

    except Exception as e:
        return f"⚠️ Không lấy được giá crypto: {e}"


def detect_crypto_request(text: str) -> Optional[list]:
    """Trả về danh sách coin được hỏi, hoặc None nếu không phải câu hỏi crypto."""
    t = text.lower()
    crypto_triggers = [
        "giá bitcoin", "giá btc", "giá eth", "giá ethereum",
        "giá coin", "giá crypto", "giá bnb", "giá sol",
        "crypto hôm nay", "coin hôm nay", "thị trường crypto",
        "bitcoin bao nhiêu", "eth bao nhiêu", "btc bao nhiêu",
        "giá doge", "giá xrp", "giá ada", "giá ton",
    ]
    if not any(kw in t for kw in crypto_triggers):
        return None

    # Tìm coin cụ thể
    found = []
    for alias, cid in CRYPTO_IDS.items():
        if alias in t and cid not in found:
            found.append(cid)

    return found if found else ["bitcoin", "ethereum", "binancecoin"]


# ================================================================
# MODULE 3: GIÁ VÀNG & NGOẠI TỆ
# ================================================================

_gold_cache = {}
GOLD_CACHE_TTL = 300  # 5 phút

def get_gold_price() -> str:
    """Giá vàng và tỷ giá USD/VND từ wttr.in và exchangerate.host."""
    results = []

    # Tỷ giá USD/VND
    try:
        resp = requests.get(
            "https://api.exchangerate-api.com/v4/latest/USD",
            timeout=5
        )
        data = resp.json()
        vnd_rate = data["rates"].get("VND", 0)
        if vnd_rate:
            results.append(f"💵 **USD/VND**: {vnd_rate:,.0f} VND")
    except Exception:
        pass

    # Giá vàng XAU/USD qua exchangerate
    try:
        resp = requests.get(
            "https://api.metals.live/v1/spot/gold",
            timeout=5
        )
        data = resp.json()
        gold_usd = data[0].get("price", 0) if isinstance(data, list) else 0
        if gold_usd:
            gold_vnd = gold_usd * 24000 / 31.1035  # oz → chỉ (3.75g)
            results.append(
                f"🥇 **Vàng thế giới**: ${gold_usd:,.2f}/oz"
                f" (~{gold_vnd/1_000_000:.1f}M VND/chỉ)"
            )
    except Exception:
        pass

    now = datetime.datetime.now().strftime("%H:%M %d/%m")
    if results:
        return f"💹 **Tỷ giá & Vàng** (cập nhật {now}):\n\n" + "\n".join(results)
    return "⚠️ Không lấy được tỷ giá lúc này."


GOLD_KEYWORDS = [
    "giá vàng", "vàng hôm nay", "vàng bao nhiêu",
    "tỷ giá", "đô la", "usd hôm nay", "usd bao nhiêu",
    "dollar", "usd/vnd", "vnd/usd",
]

def detect_gold_request(text: str) -> bool:
    return any(kw in text.lower() for kw in GOLD_KEYWORDS)


# ================================================================
# MODULE 4: THỜI TIẾT CHI TIẾT — wttr.in (free, không cần key)
# ================================================================

# ================================================================
# 🆕 WEATHER v2 — Open-Meteo (100% free, không cần API key, không
# giới hạn ẩn, không thẻ tín dụng). Thay thế wttr.in cũ vì:
#   - wttr.in hay timeout/instability, dữ liệu đôi khi cũ
#   - Open-Meteo tổng hợp từ nhiều mô hình khí tượng (ECMWF, GFS,
#     ICON...) → chính xác hơn, đúng như Gemini đã chỉ ra khi so
#     sánh với nguồn NCHMF chính thống của VN.
#   - Có UV index, mưa mm, gió, độ ẩm — đủ field như bản cũ.
# Docs: https://open-meteo.com/en/docs (free, unlimited cho non-
# commercial, không cần đăng ký/API key).
# ================================================================

_OM_GEOCODE_CACHE: dict = {}

def _om_geocode(location: str) -> Optional[dict]:
    """Tra cứu toạ độ (lat/lon) từ tên địa điểm — dùng Open-Meteo
    Geocoding API (free, không key). Cache trong RAM để đỡ gọi lại."""
    key = location.strip().lower()
    if key in _OM_GEOCODE_CACHE:
        return _OM_GEOCODE_CACHE[key]
    try:
        resp = requests.get(
            "https://geocoding-api.open-meteo.com/v1/search",
            params={"name": location, "count": 1, "language": "vi", "format": "json"},
            timeout=8,
        )
        data = resp.json()
        results = data.get("results") or []
        if not results:
            return None
        r = results[0]
        info = {
            "lat": r.get("latitude"),
            "lon": r.get("longitude"),
            "name": r.get("name", location),
            "admin1": r.get("admin1", ""),
            "country": r.get("country", ""),
        }
        _OM_GEOCODE_CACHE[key] = info
        return info
    except Exception:
        return None


# Mã thời tiết WMO (chuẩn Open-Meteo dùng) → mô tả tiếng Việt + emoji
_WMO_CODES = {
    0: ("Trời quang", "☀️"), 1: ("Ít mây", "🌤️"), 2: ("Có mây", "⛅"),
    3: ("Nhiều mây/âm u", "☁️"), 45: ("Sương mù", "🌫️"), 48: ("Sương mù đóng băng", "🌫️"),
    51: ("Mưa phùn nhẹ", "🌦️"), 53: ("Mưa phùn", "🌦️"), 55: ("Mưa phùn dày", "🌧️"),
    61: ("Mưa nhẹ", "🌦️"), 63: ("Mưa vừa", "🌧️"), 65: ("Mưa to", "🌧️"),
    66: ("Mưa đá nhẹ", "🌨️"), 67: ("Mưa đá to", "🌨️"),
    71: ("Tuyết nhẹ", "🌨️"), 73: ("Tuyết vừa", "🌨️"), 75: ("Tuyết to", "❄️"),
    80: ("Mưa rào nhẹ", "🌦️"), 81: ("Mưa rào vừa", "🌧️"), 82: ("Mưa rào dữ dội", "⛈️"),
    95: ("Dông", "⛈️"), 96: ("Dông kèm mưa đá nhẹ", "⛈️"), 99: ("Dông kèm mưa đá to", "⛈️"),
}

def get_weather_detail(location: str = "Ho Chi Minh City") -> str:
    """
    Thời tiết chi tiết từ Open-Meteo — miễn phí 100%, không cần key,
    không thẻ, không giới hạn ẩn. Chính xác hơn wttr.in (bản cũ),
    dùng dữ liệu tổng hợp từ nhiều mô hình khí tượng (ECMWF/GFS/ICON).
    """
    geo = _om_geocode(location)
    if not geo:
        return f"⚠️ Không tìm thấy địa điểm '{location}' — Boss thử ghi rõ hơn (VD: 'Đà Nẵng, Việt Nam') nhé."

    try:
        resp = requests.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": geo["lat"], "longitude": geo["lon"],
                "current": "temperature_2m,relative_humidity_2m,apparent_temperature,"
                           "precipitation,weather_code,wind_speed_10m,wind_gusts_10m,uv_index",
                # 🆕 minutely_15: dữ liệu cập nhật mỗi 15 phút — trễ pha THẤP
                # HƠN NHIỀU so với chỉ dùng "current" (nội suy theo giờ chẵn
                # của mô hình, có thể lệch 30-60+ phút khi thời tiết biến
                # động nhanh — VD mưa dông/bão đến đột ngột, đúng tình huống
                # Gemini phát hiện: Yui báo "nhiều mây" trong khi thực tế
                # đang mưa to do hoàn lưu bão). Lấy điểm dữ liệu 15-phút GẦN
                # NHẤT với hiện tại làm nguồn chính, "current" chỉ dùng dự
                # phòng nếu minutely_15 không trả về được.
                "minutely_15": "temperature_2m,relative_humidity_2m,apparent_temperature,"
                               "precipitation,weather_code,wind_speed_10m,wind_gusts_10m",
                "daily": "temperature_2m_max,temperature_2m_min,weather_code,uv_index_max,precipitation_sum",
                "timezone": "auto",
                "forecast_days": 2,
                "forecast_minutely_15": 4,  # chỉ cần vài điểm gần nhất, đỡ payload
            },
            timeout=8,
        )
        data = resp.json()
        daily = data["daily"]

        # 🆕 Ưu tiên điểm minutely_15 GẦN THỜI ĐIỂM HIỆN TẠI NHẤT (index 0
        # của mảng minutely_15 luôn là mốc 15-phút gần nhất đã qua/đang tới,
        # trễ tối đa ~15 phút thay vì tối đa ~60 phút như "current" mặc định)
        cur_source = "minutely_15"
        m15 = data.get("minutely_15")
        if m15 and m15.get("temperature_2m") and m15["temperature_2m"][0] is not None:
            cur = {
                "temperature_2m": m15["temperature_2m"][0],
                "relative_humidity_2m": m15["relative_humidity_2m"][0],
                "apparent_temperature": m15["apparent_temperature"][0],
                "precipitation": m15.get("precipitation", [0])[0] or 0,
                "weather_code": m15["weather_code"][0],
                "wind_speed_10m": m15["wind_speed_10m"][0],
                "wind_gusts_10m": (m15.get("wind_gusts_10m") or [None])[0],
                "uv_index": data.get("current", {}).get("uv_index"),  # UV không có ở minutely_15
            }
        else:
            cur_source = "current (fallback — minutely_15 không khả dụng)"
            cur = data["current"]
            cur.setdefault("wind_gusts_10m", None)

        temp_c   = cur["temperature_2m"]
        feels_c  = cur["apparent_temperature"]
        humidity = cur["relative_humidity_2m"]
        wind_kmh = cur["wind_speed_10m"]
        gust_kmh = cur.get("wind_gusts_10m")
        precip   = cur.get("precipitation", 0) or 0
        uv       = cur.get("uv_index")
        wcode    = cur["weather_code"]
        desc, emoji = _WMO_CODES.get(wcode, ("Không rõ", "🌡️"))

        place_full = geo["name"]
        if geo.get("admin1") and geo["admin1"] != geo["name"]:
            place_full += f", {geo['admin1']}"

        now_str = datetime.datetime.now().strftime("%H:%M %d/%m")
        result = [
            f"{emoji} **Thời tiết {place_full}** ({now_str}):\n",
            f"🌡️ Nhiệt độ hiện tại: **{temp_c}°C** (cảm giác {feels_c}°C)",
            f"☁️ Tình trạng: **{desc}**",
            f"💧 Độ ẩm: {humidity}% | 💨 Gió: {wind_kmh} km/h" + (f" (giật {gust_kmh} km/h)" if gust_kmh else ""),
        ]
        if precip and precip > 0:
            result.append(f"🌧️ Lượng mưa: {precip}mm")
        if uv is not None:
            result.append(f"☀️ UV index: {uv}")

        try:
            tmax_today = daily["temperature_2m_max"][0]
            tmin_today = daily["temperature_2m_min"][0]
            psum_today = (daily.get("precipitation_sum") or [None])[0]
            today_line = f"\n📅 Hôm nay: {tmin_today}°C – {tmax_today}°C"
            if psum_today:
                today_line += f" | 🌧️ mưa dự kiến {psum_today}mm"
            result.append(today_line)
            if len(daily["temperature_2m_max"]) > 1:
                tmax_tmr = daily["temperature_2m_max"][1]
                tmin_tmr = daily["temperature_2m_min"][1]
                result.append(f"📅 Ngày mai: {tmin_tmr}°C – {tmax_tmr}°C")
        except (KeyError, IndexError):
            pass

        # 🆕 Cảnh báo PHÂN CẤP theo mức độ nghiêm trọng thật của weather_code
        # — trước đây MỌI wcode>=51 (từ mưa phùn nhẹ đến dông bão dữ dội)
        # đều chỉ nhận 1 câu nhẹ "nhớ mang dù", nên khi có dông/bão thật
        # (wcode 95-99) hoặc mưa rào dữ dội (82), Yui vẫn nói nhẹ như mưa
        # phùn bình thường — đúng lỗi Gemini phát hiện (báo "nhiều mây"
        # nhẹ nhàng trong khi thực tế đang bão). Giờ tách rõ 3 cấp.
        if temp_c >= 35:
            result.append("\n⚠️ *Trời nóng — nhớ uống nước nhiều nha!*")
        elif temp_c <= 20:
            result.append("\n🧥 *Trời lạnh — mặc thêm áo vào nha!*")

        if wcode in (95, 96, 99) or (gust_kmh and gust_kmh >= 50):
            result.append("\n🚨 **CẢNH BÁO: Dông/bão — gió giật mạnh, sấm chớp. Boss hạn chế ra ngoài, tránh trú dưới cây to/cột điện!**")
        elif wcode in (65, 82) or (precip and precip >= 15):
            result.append("\n⛈️ *Mưa to/mưa rào dữ dội — nếu ra ngoài nhớ áo mưa, cẩn thận đường trơn trượt và ngập cục bộ!*")
        elif wcode >= 51:
            result.append("☂️ *Có mưa — nhớ mang dù nha!*")

        if uv is not None and uv >= 8:
            result.append("🕶️ *UV cao — che chắn kỹ khi ra ngoài nha!*")

        result.append(f"\n_(Nguồn: Open-Meteo, dữ liệu {cur_source})_")
        return "\n".join(result)

    except Exception as e:
        return f"⚠️ Không lấy được thời tiết: {e}"


WEATHER_KEYWORDS = [
    "thời tiết", "trời hôm nay", "nhiệt độ",
    "mưa không", "nắng không", "có mưa",
    "weather", "dự báo thời tiết",
]

def detect_weather_request(text: str) -> Optional[str]:
    """Trả về địa điểm nếu là câu hỏi thời tiết, None nếu không."""
    t = text.lower()
    # Cũng trigger nếu tên thành phố + từ thời tiết xuất hiện
    city_weather = any(city in t for city in [
        "hà nội", "sài gòn", "hcm", "đà nẵng", "huế", "cần thơ",
        "nha trang", "đà lạt", "vũng tàu", "hải phòng", "bình dương"
    ]) and any(kw in t for kw in ["hôm nay", "thế nào", "ra sao", "mưa", "nắng"])
    
    if not any(kw in t for kw in WEATHER_KEYWORDS) and not city_weather:
        return None

    # Tìm địa điểm
    loc_patterns = [
        r'thời tiết\s+(?:ở\s+|tại\s+)?(.+?)(?:\s+hôm|$|\?|!)',
        r'(?:ở|tại|tại\s+thành\s+phố)\s+(.+?)(?:\s+thời tiết|\s+mưa|\s+nắng|$)',
        r'(.+?)\s+(?:thời tiết|trời hôm nay|có mưa|nắng không)',
    ]
    for pat in loc_patterns:
        m = re.search(pat, t)
        if m:
            loc = m.group(1).strip()
            if 2 < len(loc) < 30:
                # Map alias → canonical ngay tại đây
                loc_map_inline = {
                    "hcm": "Ho Chi Minh City", "sài gòn": "Ho Chi Minh City",
                    "sg": "Ho Chi Minh City", "hồ chí minh": "Ho Chi Minh City",
                    "ho chi minh": "Ho Chi Minh City", "tphcm": "Ho Chi Minh City",
                    "hà nội": "Hanoi", "ha noi": "Hanoi", "hn": "Hanoi",
                    "đà nẵng": "Da Nang", "huế": "Hue", "cần thơ": "Can Tho",
                    "nha trang": "Nha Trang", "đà lạt": "Da Lat",
                    "vũng tàu": "Vung Tau", "hải phòng": "Hai Phong",
                }
                loc_lower2 = loc.lower()
                for alias2, canonical2 in loc_map_inline.items():
                    if alias2 in loc_lower2:
                        return canonical2
                return loc

    return "Ho Chi Minh City"  # Mặc định


# ================================================================
# MODULE 5: QR CODE — thư viện qrcode (offline, cực nhẹ)
# ================================================================

def generate_qr(content: str) -> str:
    """
    Tạo QR code từ text/link.
    Trả về base64 PNG để Gradio hiển thị, hoặc link QR online.
    """
    if not content:
        return "⚠️ Bạn muốn tạo QR cho nội dung gì?"

    # Dùng API online (không cần install thêm gì)
    encoded = requests.utils.quote(content)
    qr_url  = f"https://api.qrserver.com/v1/create-qr-code/?data={encoded}&size=300x300&ecc=M"

    return (
        f"📱 **QR Code đã tạo:**\n\n"
        f"![QR Code]({qr_url})\n\n"
        f"📋 **Nội dung:** `{content[:100]}`\n"
        f"🔗 **Link ảnh:** {qr_url}\n\n"
        f"*Dùng camera quét QR ở trên nhé!*"
    )


QR_KEYWORDS = [
    "tạo qr", "tạo mã qr", "qr code", "qrcode",
    "mã qr cho", "qr cho link", "qr cho text",
    "generate qr", "make qr",
]

def detect_qr_request(text: str) -> Optional[str]:
    """Trả về nội dung cần tạo QR, hoặc None."""
    t = text.lower()
    if not any(kw in t for kw in QR_KEYWORDS):
        return None

    # Tách nội dung
    patterns = [
        r'(?:tạo qr|tạo mã qr|qr code|qr cho)\s+(?:cho\s+|link\s+|text\s+)?(.+)',
        r'(?:generate|make)\s+qr\s+(?:for\s+|code\s+for\s+)?(.+)',
    ]
    for pat in patterns:
        m = re.search(pat, t)
        if m:
            content = m.group(1).strip()
            if content:
                return content

    # Tìm URL trong text
    url_m = re.search(r'https?://\S+', text)
    if url_m:
        return url_m.group(0)

    return None


# ================================================================
# MODULE 6: NHẬT KÝ CẢM XÚC — dùng SQLite đã có sẵn
# ================================================================

_journal_db_init = False

def _ensure_journal_db():
    """Tạo bảng journal nếu chưa có."""
    global _journal_db_init
    if _journal_db_init:
        return
    import sqlite3
    try:
        conn = sqlite3.connect(_URAL_DB_PATH)
        conn.execute("""
            CREATE TABLE IF NOT EXISTS mood_journal (
                id        INTEGER PRIMARY KEY AUTOINCREMENT,
                username  TEXT    NOT NULL,
                timestamp TEXT    NOT NULL,
                mood      INTEGER NOT NULL,
                note      TEXT,
                keywords  TEXT
            )
        """)
        conn.commit()
        conn.close()
        _journal_db_init = True
    except Exception as e:
        print(f"⚠️ [Journal] DB init lỗi: {e}")


def save_mood_entry(username: str, mood: int, note: str = "") -> str:
    """Lưu trạng thái cảm xúc vào nhật ký."""
    _ensure_journal_db()
    import sqlite3
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
    try:
        conn = sqlite3.connect(_URAL_DB_PATH)
        conn.execute(
            "INSERT INTO mood_journal (username, timestamp, mood, note) VALUES (?,?,?,?)",
            (username, now, mood, note[:500])
        )
        conn.commit()
        conn.close()

        mood_labels = {0: "😔 Rất buồn", 1: "😕 Buồn", 2: "😐 Bình thường",
                       3: "😊 Vui", 4: "🥳 Rất vui"}
        label = mood_labels.get(mood, "😐")
        return f"✅ Đã lưu nhật ký cảm xúc!\n📅 {now}\n{label}\n📝 {note[:100] if note else '(không có ghi chú)'}"
    except Exception as e:
        return f"⚠️ Lỗi lưu nhật ký: {e}"


def get_mood_history(username: str, days: int = 7) -> str:
    """Lấy lịch sử cảm xúc 7 ngày gần nhất."""
    _ensure_journal_db()
    import sqlite3
    try:
        cutoff = (datetime.datetime.now() -
                  datetime.timedelta(days=days)).strftime("%Y-%m-%d")
        conn = sqlite3.connect(_URAL_DB_PATH)
        rows = conn.execute(
            "SELECT timestamp, mood, note FROM mood_journal "
            "WHERE username=? AND timestamp>=? ORDER BY timestamp DESC LIMIT 20",
            (username, cutoff)
        ).fetchall()
        conn.close()

        if not rows:
            return "Chưa có nhật ký cảm xúc nào trong 7 ngày qua."

        mood_labels = {0: "😔", 1: "😕", 2: "😐", 3: "😊", 4: "🥳"}
        lines = [f"📔 **Nhật ký cảm xúc {days} ngày qua:**\n"]
        for ts, mood, note in rows:
            icon = mood_labels.get(mood, "😐")
            note_str = f" — {note[:60]}" if note else ""
            lines.append(f"{icon} `{ts}` {note_str}")

        # Tính trung bình
        avg = sum(r[1] for r in rows) / len(rows)
        avg_label = mood_labels.get(round(avg), "😐")
        lines.append(f"\n📊 Trung bình: {avg_label} ({avg:.1f}/4)")

        return "\n".join(lines)
    except Exception as e:
        return f"⚠️ Lỗi đọc nhật ký: {e}"


JOURNAL_KEYWORDS_SAVE = [
    "ghi lại cảm xúc", "lưu nhật ký", "tôi đang",
    "hôm nay mình", "hôm nay tôi", "cảm thấy",
]
JOURNAL_KEYWORDS_READ = [
    "nhật ký cảm xúc", "lịch sử cảm xúc", "cảm xúc gần đây",
    "tôi hay cảm thấy", "xem nhật ký",
]


# ================================================================
# MODULE 7: ĐỔI ĐƠN VỊ — Logic thuần, không cần mạng
# ================================================================

UNIT_CONVERSIONS = {
    # Độ dài
    ("km", "m"):    1000,      ("m", "km"):    0.001,
    ("m", "cm"):    100,       ("cm", "m"):    0.01,
    ("cm", "mm"):   10,        ("mm", "cm"):   0.1,
    ("km", "mile"): 0.621371,  ("mile", "km"): 1.60934,
    ("m", "ft"):    3.28084,   ("ft", "m"):    0.3048,
    ("m", "inch"):  39.3701,   ("inch", "m"):  0.0254,
    # Khối lượng
    ("kg", "g"):    1000,      ("g", "kg"):    0.001,
    ("kg", "lb"):   2.20462,   ("lb", "kg"):   0.453592,
    ("kg", "ton"):  0.001,     ("ton", "kg"):  1000,
    # Nhiệt độ — xử lý riêng
    # Diện tích
    ("m2", "cm2"):  10000,     ("cm2", "m2"):  0.0001,
    ("km2", "m2"):  1000000,   ("m2", "km2"):  0.000001,
    ("ha", "m2"):   10000,     ("m2", "ha"):   0.0001,
    # Dung tích
    ("l", "ml"):    1000,      ("ml", "l"):    0.001,
    ("l", "m3"):    0.001,     ("m3", "l"):    1000,
    # Tốc độ
    ("kmh", "ms"):  0.277778,  ("ms", "kmh"):  3.6,
    ("kmh", "mph"): 0.621371,  ("mph", "kmh"): 1.60934,
}

def convert_unit(text: str) -> Optional[str]:
    """
    Đổi đơn vị từ câu hỏi tự nhiên.
    VD: "100 km bằng bao nhiêu mile?" → "100 km = 62.14 mile"
    """
    t = text.lower()

    # Nhiệt độ
    c_to_f = re.search(r'(\d+\.?\d*)\s*°?c\s+(?:ra|sang|bằng|=|to)\s+(?:°?f|fahrenheit)', t)
    f_to_c = re.search(r'(\d+\.?\d*)\s*°?f\s+(?:ra|sang|bằng|=|to)\s+(?:°?c|celsius)', t)
    if c_to_f:
        c = float(c_to_f.group(1))
        return f"🌡️ **{c}°C** = **{c * 9/5 + 32:.1f}°F**"
    if f_to_c:
        f = float(f_to_c.group(1))
        return f"🌡️ **{f}°F** = **{(f - 32) * 5/9:.1f}°C**"

    # Đơn vị thông thường
    pattern = r'(\d+\.?\d*)\s*([a-zA-Záàảãạăắặẳẵấầẩẫậéèẻẽẹêếềểễệíìỉĩịóòỏõọôốồổỗộơớờởỡợúùủũụưứừửữựýỳỷỹỵđ²³/]+)\s+(?:ra|sang|bằng|=|to|thành)\s+([a-zA-Záàảãạ²³/]+)'
    m = re.search(pattern, t)
    if not m:
        # Thử pattern đơn giản hơn: "100 km sang m"
        m = re.search(r'(\d+\.?\d*)\s*(km|m|cm|mm|kg|g|lb|l|ml|kmh|mph|mile|ft|inch|ha|ton)\s+(?:sang|ra|đổi|thành|=|to)\s+(km|m|cm|mm|kg|g|lb|l|ml|kmh|mph|mile|ft|inch|ha|ton)', t)
    if not m:
        # Pattern "100km to m"
        m = re.search(r'(\d+\.?\d*)\s*(km|m|cm|mm|kg|g|lb|l|ml|kmh|mph|mile|ft|inch|ha|ton)\s+.*?\s+(km|m|cm|mm|kg|g|lb|l|ml|kmh|mph|mile|ft|inch|ha|ton)\b', t)

    if m:
        try:
            value = float(m.group(1))
            from_unit = m.group(2).strip().lower().replace('²', '2').replace('³', '3')
            to_unit   = m.group(3).strip().lower().replace('²', '2').replace('³', '3')

            factor = UNIT_CONVERSIONS.get((from_unit, to_unit))
            if factor:
                result = value * factor
                return f"📐 **{value} {from_unit}** = **{result:,.4f} {to_unit}**"
        except Exception:
            pass

    return None


UNIT_KEYWORDS = [
    "đổi", "convert", "bằng bao nhiêu", "ra bao nhiêu",
    "sang bao nhiêu", "km sang", "kg sang", "°c sang", "°f sang",
    "m sang", "l sang", "đổi đơn vị",
]

def detect_unit_request(text: str) -> bool:
    t = text.lower()
    return any(kw in t for kw in UNIT_KEYWORDS)


# ================================================================
# MODULE 8: TRA TỪ ĐIỂN — Free Dictionary API
# ================================================================

def lookup_word(word: str) -> str:
    """Tra nghĩa từ tiếng Anh qua Free Dictionary API."""
    word = word.strip().lower()
    try:
        resp = requests.get(
            f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}",
            timeout=6
        )
        if resp.status_code != 200:
            return f"⚠️ Không tìm thấy từ '{word}' trong từ điển."

        data = resp.json()[0]
        phonetic = data.get("phonetic", "")
        meanings = data.get("meanings", [])

        lines = [f"📖 **{word}** {phonetic}\n"]
        for m in meanings[:2]:
            part = m.get("partOfSpeech", "")
            lines.append(f"*{part}*:")
            for defn in m.get("definitions", [])[:2]:
                d = defn.get("definition", "")
                ex = defn.get("example", "")
                lines.append(f"  • {d}")
                if ex:
                    lines.append(f"    → _{ex}_")
            synonyms = m.get("synonyms", [])[:3]
            if synonyms:
                lines.append(f"  Từ đồng nghĩa: {', '.join(synonyms)}")
            lines.append("")

        return "\n".join(lines)
    except Exception as e:
        return f"⚠️ Lỗi tra từ: {e}"


DICT_KEYWORDS = [
    "tra từ", "nghĩa của", "from nghĩa là gì",
    "mean", "definition of", "define",
    "từ điển", " là gì trong tiếng anh",
    "tiếng anh là gì",
]

def detect_dict_request(text: str) -> Optional[str]:
    """Trả về từ cần tra, hoặc None."""
    t = text.lower()
    if not any(kw in t for kw in DICT_KEYWORDS):
        return None

    patterns = [
        r'(?:nghĩa của|tra từ|define|definition of)\s+"?([a-zA-Z\s]+)"?',
        r'"?([a-zA-Z\s]+)"?\s+(?:nghĩa là gì|mean|là gì)',
        r'(?:từ|word)\s+"?([a-zA-Z]+)"?',
    ]
    for pat in patterns:
        m = re.search(pat, t)
        if m:
            word = m.group(1).strip()
            if 1 < len(word) < 30 and word.replace(" ", "").isalpha():
                return word

    return None


# ================================================================
# MODULE 9: QUIZ / TRÒ CHƠI — nâng cấp quiz_game cũ
# ================================================================

QUIZ_BANK = {
    "kiến thức": [
        {"q": "Thủ đô của Việt Nam là gì?", "a": "Hà Nội", "opts": ["Hà Nội", "TP.HCM", "Đà Nẵng", "Huế"]},
        {"q": "Núi cao nhất Việt Nam?", "a": "Fansipan", "opts": ["Fansipan", "Bạch Mã", "Ngọc Linh", "Pu Si Lung"]},
        {"q": "Sông dài nhất Việt Nam?", "a": "Sông Mekong (Cửu Long)", "opts": ["Sông Hồng", "Sông Mekong (Cửu Long)", "Sông Đà", "Sông Mã"]},
        {"q": "Việt Nam có bao nhiêu tỉnh thành?", "a": "63", "opts": ["61", "63", "64", "65"]},
        {"q": "ChatGPT được phát triển bởi công ty nào?", "a": "OpenAI", "opts": ["Google", "OpenAI", "Meta", "Microsoft"]},
        {"q": "Đồng tiền của Nhật Bản là gì?", "a": "Yên (JPY)", "opts": ["Won", "Yên (JPY)", "Nhân dân tệ", "Đô la HK"]},
        {"q": "1 km bằng bao nhiêu mét?", "a": "1000 m", "opts": ["100 m", "1000 m", "10000 m", "500 m"]},
        {"q": "Hành tinh lớn nhất Hệ Mặt Trời?", "a": "Sao Mộc (Jupiter)", "opts": ["Sao Thổ", "Sao Mộc (Jupiter)", "Sao Hải Vương", "Mặt Trời"]},
    ],
    "công nghệ": [
        {"q": "CPU viết tắt của từ gì?", "a": "Central Processing Unit", "opts": ["Computer Power Unit", "Central Processing Unit", "Core Processing Utility", "Central Program Unit"]},
        {"q": "HTTP viết tắt của gì?", "a": "HyperText Transfer Protocol", "opts": ["HyperText Transfer Protocol", "High Tech Transfer Protocol", "Hyper Transfer Text Protocol", "Host Text Transfer Protocol"]},
        {"q": "RAM là viết tắt của?", "a": "Random Access Memory", "opts": ["Random Access Memory", "Read Access Memory", "Runtime Access Memory", "Rapid Access Module"]},
        {"q": "Python được tạo ra bởi ai?", "a": "Guido van Rossum", "opts": ["James Gosling", "Guido van Rossum", "Linus Torvalds", "Dennis Ritchie"]},
    ],
    "toán vui": [
        {"q": "1 + 1 × 2 = ?", "a": "3", "opts": ["2", "3", "4", "1"]},
        {"q": "Căn bậc hai của 144 = ?", "a": "12", "opts": ["11", "12", "13", "14"]},
        {"q": "2^10 = ?", "a": "1024", "opts": ["512", "1024", "2048", "256"]},
        {"q": "Số π (pi) xấp xỉ bằng?", "a": "3.14159", "opts": ["3.14159", "3.14169", "3.14150", "3.15149"]},
    ],
}

_active_quiz = {}  # {username: {"q": ..., "a": ..., "category": ...}}

def start_quiz(username: str, category: str = "") -> str:
    """Bắt đầu quiz, trả về câu hỏi."""
    # Chọn danh mục
    if not category:
        category = random.choice(list(QUIZ_BANK.keys()))
    else:
        for cat in QUIZ_BANK:
            if cat in category.lower():
                category = cat
                break
        else:
            category = random.choice(list(QUIZ_BANK.keys()))

    questions = QUIZ_BANK.get(category, QUIZ_BANK["kiến thức"])
    q = random.choice(questions)

    _active_quiz[username] = {
        "q": q["q"], "a": q["a"],
        "opts": q.get("opts", []), "category": category
    }

    opts_str = ""
    if q.get("opts"):
        opts_str = "\n\n" + "\n".join(
            [f"  {chr(65+i)}. {opt}" for i, opt in enumerate(q["opts"])]
        )

    return (
        f"🎯 **Quiz — {category.title()}**\n\n"
        f"**{q['q']}**{opts_str}\n\n"
        f"*Trả lời để Yui chấm điểm nhé!*"
    )


def check_quiz_answer(username: str, answer: str) -> Optional[str]:
    """Kiểm tra đáp án quiz đang chờ."""
    if username not in _active_quiz:
        return None

    quiz = _active_quiz[username]
    correct = quiz["a"]
    ans_lower = answer.lower().strip()
    correct_lower = correct.lower()

    # Khớp chữ cái lựa chọn (A/B/C/D)
    letter_match = re.match(r'^([abcd])[\.\)]?\s*$', ans_lower)
    if letter_match and quiz.get("opts"):
        idx = ord(letter_match.group(1)) - ord('a')
        if 0 <= idx < len(quiz["opts"]):
            ans_lower = quiz["opts"][idx].lower()

    is_correct = (correct_lower in ans_lower or ans_lower in correct_lower)
    del _active_quiz[username]

    if is_correct:
        return (
            f"✅ **Chính xác!** 🎉\n"
            f"Đáp án đúng: **{correct}**\n\n"
            f"Muốn tiếp tục? Nói *'quiz tiếp'* nha!"
        )
    else:
        return (
            f"❌ **Sai rồi!**\n"
            f"Đáp án đúng là: **{correct}**\n\n"
            f"Đừng nản, thử câu khác nhé! Nói *'quiz tiếp'* nha!"
        )


QUIZ_KEYWORDS = [
    "quiz", "đố", "câu hỏi", "trắc nghiệm",
    "chơi đố vui", "đố vui", "thi kiến thức",
    "quiz tiếp", "câu tiếp", "câu khác",
]

def detect_quiz_request(text: str) -> bool:
    return any(kw in text.lower() for kw in QUIZ_KEYWORDS)


# ================================================================
# HOOK VÀO urals_brain — xử lý tất cả module trước khi gọi AI
# ================================================================

def _patch_urals_brain_mega():
    """Hook vào urals_brain để xử lý tất cả module mới."""
    target = None
    for mod in sys.modules.values():
        if hasattr(mod, 'urals_brain') and hasattr(mod, 'ask_logic_brain'):
            target = mod
            break
    if not target:
        return False

    _orig_brain = target.urals_brain

    def _mega_brain(message, history, request=None):
        # Lấy text
        if isinstance(message, dict):
            user_text = message.get("text", "")
            has_file  = bool(message.get("files"))
        else:
            user_text = str(message)
            has_file  = False

        if has_file or not user_text.strip():
            return _orig_brain(message, history, request)

        username = request.username if request else "guest"
        t = user_text.lower()

        # ── MODULE 1: TIN TỨC ────────────────────────────────────
        news_triggers = ["tin tức", "tin mới", "tin hot", "news", "báo hôm nay",
                         "tin thế giới", "tin công nghệ", "tin thể thao",
                         "tin kinh doanh", "tin giải trí"]
        if any(kw in t for kw in news_triggers):
            # Tìm danh mục
            cat = ""
            for kw in ["thế giới", "quốc tế", "công nghệ", "tech",
                       "kinh doanh", "kinh tế", "giải trí", "thể thao", "bóng đá"]:
                if kw in t:
                    cat = kw
                    break
            return get_news(cat, num=5)

        # ── MODULE 2: CRYPTO ─────────────────────────────────────
        coins = detect_crypto_request(user_text)
        if coins is not None:
            return get_crypto_price(coins)

        # ── MODULE 3: GIÁ VÀNG / TỶ GIÁ ────────────────────────
        if detect_gold_request(user_text):
            return get_gold_price()

        # ── MODULE 4: THỜI TIẾT ──────────────────────────────────
        location = detect_weather_request(user_text)
        if location is not None:
            return get_weather_detail(location)

        # ── MODULE 5: QR CODE ────────────────────────────────────
        qr_content = detect_qr_request(user_text)
        if qr_content is not None:
            return generate_qr(qr_content)

        # ── MODULE 6: NHẬT KÝ ────────────────────────────────────
        if any(kw in t for kw in JOURNAL_KEYWORDS_READ):
            return get_mood_history(username, days=7)

        # ── MODULE 7: ĐỔI ĐƠN VỊ ────────────────────────────────
        if detect_unit_request(user_text):
            result = convert_unit(user_text)
            if result:
                return result

        # ── MODULE 8: TỪ ĐIỂN ────────────────────────────────────
        word = detect_dict_request(user_text)
        if word is not None:
            return lookup_word(word)

        # ── MODULE 9: QUIZ — ĐÃ TẮT (quá phiền) ─────────────────
        # if detect_quiz_request(user_text): ...  # Disabled by admin

        # Không match → chạy bình thường
        return _orig_brain(message, history, request)

    target.urals_brain = _mega_brain
    print("[Mega Module v1.0] Hook vào urals_brain thành công!")
    return True


class _MegaHook:
    def __init__(self):
        self._done = False

    def install(self):
        if _patch_urals_brain_mega():
            self._done = True
            return
        import builtins
        _orig_import = builtins.__import__
        outer = self

        def _hooked(name, *a, **kw):
            mod = _orig_import(name, *a, **kw)
            level = a[4] if len(a) >= 5 else kw.get("level", 0)
            if not outer._done and ("." not in str(name)) and (level == 0):
                try:
                    if hasattr(mod, 'urals_brain') and hasattr(mod, 'ask_logic_brain'):
                        if _patch_urals_brain_mega():
                            outer._done = True
                            builtins.__import__ = _orig_import
                except Exception:
                    pass
            return mod

        builtins.__import__ = _hooked
        print("[Mega Module v1.0] Đang chờ app.py load...")


_hook = _MegaHook()
_hook.install()

print("""
+======================================================+
|   URAL MEGA MODULE v1.0                             |
|                                                     |
|   9 module — 100% free, không key, không phí ẩn:  |
|   [1] Tin tức realtime  — RSS VnExpress/TuoiTre    |
|   [2] Giá Crypto        — CoinGecko API free       |
|   [3] Giá Vàng/USD      — ExchangeRate API free    |
|   [4] Thời tiết         — wttr.in (chi tiết hơn)   |
|   [5] QR Code           — qrserver.com API free    |
|   [6] Nhật ký cảm xúc  — SQLite đã có sẵn         |
|   [7] Đổi đơn vị       — Logic thuần, offline      |
|   [8] Từ điển EN        — Free Dictionary API      |
|   [9] Quiz / Trò chơi  — 4 danh mục câu hỏi       |
|                                                     |
|   HF Free Tier: RAM +~2MB, CPU gần như không đổi   |
+======================================================+
""")


# ================================================================
# TEST
# ================================================================
if __name__ == "__main__":
    import asyncio

    print("=" * 58)
    print("TEST MEGA MODULE v1.0")
    print("=" * 58)

    passed = failed = 0

    def check(name, result, expect_contains):
        global passed, failed
        ok = expect_contains.lower() in result.lower()
        print(f"{'✅' if ok else '❌'} [{name}]")
        if not ok:
            print(f"   Got: {result[:80]}")
            failed += 1
        else:
            passed += 1

    # Module 2: Crypto detection
    print("\n--- Module 2: Crypto ---")
    check("detect btc", str(detect_crypto_request("giá bitcoin hôm nay")), "bitcoin")
    check("detect eth", str(detect_crypto_request("giá eth bao nhiêu")), "ethereum")
    check("no crypto",  str(detect_crypto_request("hôm nay ăn gì")), "None")

    # Module 3: Gold detection
    print("\n--- Module 3: Gold ---")
    check("detect gold",  str(detect_gold_request("giá vàng hôm nay")), "True")
    check("detect usd",   str(detect_gold_request("tỷ giá usd hôm nay")), "True")
    check("no gold",      str(detect_gold_request("tôi đói quá")), "False")

    # Module 4: Weather detection
    print("\n--- Module 4: Weather ---")
    check("detect weather HCM", str(detect_weather_request("thời tiết hồ chí minh")), "Ho Chi Minh")
    check("detect weather HN",  str(detect_weather_request("trời hà nội hôm nay thế nào")), "Hanoi")

    check("no weather",         str(detect_weather_request("ăn gì ngon không")), "None")

    # Module 5: QR detection
    print("\n--- Module 5: QR Code ---")
    check("detect qr url",  str(detect_qr_request("tạo qr cho link https://google.com")), "google.com")
    check("detect qr text", str(detect_qr_request("tạo mã qr cho xin chào")), "xin chào")
    check("no qr",          str(detect_qr_request("hôm nay thứ mấy")), "None")

    # Module 7: Unit conversion
    print("\n--- Module 7: Đổi đơn vị ---")
    check("km to m",  convert_unit("100 km sang m"), "100,000")
    check("kg to lb", convert_unit("70 kg đổi ra lb"), "154")
    check("C to F",   convert_unit("37°C sang °F"), "98")

    # Module 8: Dictionary detection
    print("\n--- Module 8: Từ điển ---")
    check("detect word",  str(detect_dict_request("nghĩa của beautiful là gì")), "beautiful")
    check("detect define",str(detect_dict_request("define algorithm")), "algorithm")
    check("no dict",      str(detect_dict_request("con mèo cute quá")), "None")

    # Module 9: Quiz
    print("\n--- Module 9: Quiz ---")
    q = start_quiz("test_user", "kiến thức")
    check("quiz start",  q, "Quiz")
    ans = check_quiz_answer("test_user", "sai hết rồi xxx yyy zzz 999")
    check("quiz wrong",  ans or "", "Sai")

    q2 = start_quiz("test_user2", "toán")
    check("quiz math",   q2, "Quiz")
    correct_ans = _active_quiz.get("test_user2", {}).get("a", "")
    if correct_ans:
        ans2 = check_quiz_answer("test_user2", correct_ans)
        check("quiz correct", ans2 or "", "Chính xác")

    # WAV wrapper (từ module 6 dùng SQLite — chỉ check không crash)
    print("\n--- Module 6: Nhật ký ---")
    result = save_mood_entry("test_user", 3, "Hôm nay vui!")
    check("save mood", result, "✅")
    hist = get_mood_history("test_user", days=7)
    check("read mood", hist, "Hôm nay vui!")

    print(f"\n{'='*58}")
    print(f"KẾT QUẢ: {passed}/{passed+failed} ({passed/(passed+failed)*100:.0f}%)")
    if failed == 0:
        print("✅ TẤT CẢ TEST PASS!")
    else:
        print(f"⚠️ {failed} test chưa pass")