# -*- coding: utf-8 -*-
"""THU_CAT_NEO — bộ test THƯỜNG TRỰC cho VIỆC "cắt theo neo, không theo số từ".

VÌ SAO TỆP NÀY TỒN TẠI
-----------------------
Trước khi sửa, `guilde/dcabot.php` (613 dòng, 11 phần có neo: step-1..5,
config-modes, signal-config, local-config, params, panel-guide,
troubleshooting — có hẳn mục lục trỏ tới) chỉ ra ĐÚNG 4 đoạn vô danh, cắt
theo ~250 từ bất kể cấu trúc, và CẢ 4 đoạn mang chung một tiêu đề ("SMART
DCA EA v119 — Hướng dẫn cài đặt"). Hậu quả: khách hỏi "EA báo lỗi không
vào lệnh" thì bot không tìm ra phần "Khắc phục lỗi thường gặp" — vì phần
đó không còn tồn tại như một thứ riêng trong chỉ mục.

Bộ này chạy trên CHỈ MỤC THẬT đã dựng (`chi_muc.sqlite`, bằng
`python kho/chi_muc.py`) — không giả lập — để đo đúng thứ khách sẽ thấy.

CA ĐỐI CHỨNG ÂM (ca 5) là ca quan trọng nhất: cắt nhỏ hơn PHẢI làm TĂNG
tổng số đoạn, không bao giờ được GIẢM — giảm nghĩa là đang mất tri thức.

Chạy:  python thu/thu_cat_neo.py
Mã thoát: 0 nếu mọi ca đạt, 1 nếu có ca trượt (hoặc chưa có chỉ mục).
"""
from __future__ import annotations

import os
import sys

_THU_MUC_CHA = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _THU_MUC_CHA not in sys.path:
    sys.path.insert(0, _THU_MUC_CHA)

from giao_uoc import doan_kieu  # noqa: E402


# Tổng số đoạn TRƯỚC KHI có VIỆC "cắt theo neo" — đo thật trên chỉ mục cũ,
# nêu rõ trong yêu cầu việc. Đây là ĐÁY, không được giảm dưới mức này.
TONG_DOAN_TRUOC = 286

TRANG_KIEM = '/guilde/dcabot'


def _doc_chi_muc_that():
    """Trả list[Doan] từ chi_muc.sqlite thật, hoặc None nếu chưa dựng."""
    duong_dan_sqlite = os.path.join(_THU_MUC_CHA, 'chi_muc.sqlite')
    if not os.path.isfile(duong_dan_sqlite):
        return None
    from kho.chi_muc import doc_tat_ca
    return doc_tat_ca(duong_dan_sqlite)


# =====================================================================
# CÁC CA KIỂM — mỗi hàm trả (dat: bool, chi_tiet: str)
# =====================================================================

def ca_1_it_nhat_9_doan(cac_doan_trang):
    n = len(cac_doan_trang)
    return n >= 9, f'{n} đoạn (kỳ vọng >= 9, trước sửa là 4)'


def ca_2_tieu_de_khac_nhau(cac_doan_trang):
    tieu_de_khac_nhau = {d.tieu_de for d in cac_doan_trang}
    n = len(tieu_de_khac_nhau)
    ok = n >= 6
    # str(...) như ở ca 3: tiêu đề có thể là None ở vài nguồn.
    chi_tiet = f'{n} tiêu đề khác nhau (kỳ vọng >= 6): {sorted(str(x) for x in tieu_de_khac_nhau)}'
    return ok, chi_tiet


def ca_3_troubleshooting_la_loi(cac_doan_trang):
    ung_vien = [d for d in cac_doan_trang if '#troubleshooting' in d.duong_dan]
    if not ung_vien:
        return False, 'KHÔNG có đoạn nào duong_dan chứa #troubleshooting'
    dung_kieu = [d for d in ung_vien if d.kieu == 'loi']
    ok = len(dung_kieu) > 0
    chi_tiet = (f'{len(ung_vien)} đoạn #troubleshooting, '
                f'{len(dung_kieu)} trong đó kieu==\'loi\' '
                # str(...) BẮT BUỘC: mục tư vấn có kieu=None, và sorted() trên
                # tập lẫn None với chuỗi thì ném TypeError. Lỗi này KHÔNG lộ ra
                # ở máy phát triển vì ở đó không có CSDL nên chỉ mục ra 0 mục
                # tư vấn — chỉ vỡ trên máy chủ, nơi có 13 mục thật.
                f'(kieu thực tế: {sorted(str(d.kieu) for d in ung_vien)})')
    return ok, chi_tiet


def ca_4_buoc_co_step(cac_doan_trang):
    ung_vien = [d for d in cac_doan_trang if d.kieu == 'buoc' and '#step-' in d.duong_dan]
    ok = len(ung_vien) > 0
    chi_tiet = (f'{len(ung_vien)} đoạn kieu==\'buoc\' có neo dạng #step- '
                f'(vd: {ung_vien[0].duong_dan if ung_vien else "(không có)"})')
    return ok, chi_tiet


def ca_5_doi_chung_am_khong_giam(tong_hien_tai):
    ok = tong_hien_tai >= TONG_DOAN_TRUOC
    if ok:
        chi_tiet = f'{tong_hien_tai} đoạn >= {TONG_DOAN_TRUOC} đoạn trước sửa — KHÔNG mất tri thức'
    else:
        chi_tiet = (f'!!! CẢNH BÁO MẤT TRI THỨC !!! '
                    f'{tong_hien_tai} đoạn < {TONG_DOAN_TRUOC} đoạn trước sửa '
                    f'— cắt nhỏ hơn PHẢI làm TĂNG số đoạn, không được giảm')
    return ok, chi_tiet


# Tám cặp (neo, tiêu_đề) — phủ cả 5 KIEU_NUT, dùng để tự kiểm doan_kieu().
_CA_TU_KIEM_DOAN_KIEU = (
    ('step-1', 'Cài đặt file EA và DLL vào MT5', 'buoc'),
    ('step-5', 'Kiểm tra hoạt động', 'buoc'),
    ('buoc-3', 'Một bước bất kỳ', 'buoc'),
    ('troubleshooting', 'Khắc phục lỗi thường gặp', 'loi'),
    ('faq-chung', 'Câu hỏi thường gặp', 'loi'),
    ('params', 'Chi Tiết Tham Số', 'tham_so'),
    ('signal-config', '📡 Cài đặt Signal Config', 'tham_so'),
    (None, 'Hướng dẫn cấu hình chung', 'tham_so'),
    ('panel-guide', 'Hướng dẫn sử dụng Panel', 'khai_niem'),
    (None, 'Giới thiệu tổng quan về sản phẩm', 'khai_niem'),
)


def ca_6_tu_kiem_doan_kieu():
    sai = []
    for neo, tieu_de, ky_vong in _CA_TU_KIEM_DOAN_KIEU:
        thuc = doan_kieu(neo, tieu_de)
        if thuc != ky_vong:
            sai.append(f'  neo={neo!r} tieu_de={tieu_de!r} -> ra {thuc!r}, kỳ vọng {ky_vong!r}')
    ok = not sai
    chi_tiet = (f'{len(_CA_TU_KIEM_DOAN_KIEU)} cặp, phủ đủ 5 kiểu' if ok
                else f'{len(sai)}/{len(_CA_TU_KIEM_DOAN_KIEU)} cặp SAI:\n' + '\n'.join(sai))
    return ok, chi_tiet



# =====================================================================
# CA 7 — NEO RỖNG (mồi rỗng)
# =====================================================================
#
# Đo thật ngày 23/09/2026 trên guilde/tradingviewea.php: 6 neo đặt id
# lên CHÍNH thẻ <h2>. Bộ cắt lấy phạm vi neo theo NGĂN XẾP THẺ, mà <h2>
# đóng ngay sau dòng tiêu đề — nên mỗi đoạn chỉ ra 20-46 ký tự, đúng
# bằng cái tiêu đề, còn 2.506 ký tự nội dung thật rơi ra đoạn VÔ DANH.
#
# Thứ này TỆ HƠN không có neo: đoạn rỗng vẫn mang tiêu đề hấp dẫn nên
# vẫn THẮNG tìm kiếm, rồi đưa cho mô hình đúng một dòng chữ. Khách hỏi
# "xử lý lỗi TradingView" thì bot nhận về 22 ký tự và bịa phần còn lại.
#
# Cách chữa: chuyển id ra thẻ BAO (div/section) chứa cả tiêu đề lẫn nội
# dung. Sau khi chữa: #troubleshooting 22 -> 579 ký tự.
#
# BA NGOẠI LỆ dưới đây KHÔNG phải lỗi, mỗi cái một lý do đã truy ra:
#   /guilde/dcabot#step-4      — chữ nằm trong HAI NEO CON (#signal-config,
#                                #local-config); bộ cắt gán chữ cho neo
#                                TRONG CÙNG, nên neo cha còn ít là đúng.
#   /guilde/dcabot#params      — nội dung vào bằng 9 lệnh include() PHP;
#   /codebot/custom#danhgia    — nội dung vào bằng require() PHP.
#                                Bộ trích đọc TỪNG TỆP, không đi theo
#                                include. Muốn sửa phải dạy bộ trích đi
#                                theo include — đổi kiến trúc, cần chốt
#                                riêng, không lặng lẽ làm kèm.
#
# ĐỐI CHỨNG ÂM: trả id về thẻ <h2> trong tradingviewea.php rồi dựng lại
# chỉ mục — ca này PHẢI trượt với 6 neo rỗng mới.
NEO_RONG_DA_BIET = frozenset((
    '/guilde/dcabot#step-4',
    '/guilde/dcabot#params',
    '/codebot/custom#danhgia',
))

# NGƯỠNG 60, không phải 100.
#
# Lỗi cần bắt sinh ra đoạn chỉ chứa ĐÚNG dòng tiêu đề — đo thật trên
# tradingviewea.php lúc chưa sửa: 20, 21, 22, 27, 30, 46 ký tự. Cả sáu
# đều dưới 60 nên ngưỡng này vẫn bắt trọn (đối chứng âm ở cuối tệp).
#
# Mốc 100 ban đầu quá rộng: nó báo nhầm cả những phần NGẮN THẬT nhưng
# đàng hoàng — ví dụ "Bước 1 — Chép EA vào MetaTrader" với hai gạch đầu
# dòng ra 92 ký tự. Phần như thế là tri thức thật, đủ để trả lời khách;
# bắt nhầm nó chỉ khiến người ta quen tay cho ca này vào danh sách bỏ
# qua, rồi lần sau lỗi thật cũng bị bỏ qua theo.
NGUONG_KY_TU = 60


def ca_8_khong_co_hai_dau_thang(cac_doan):
    """Không đường dẫn trích nguồn nào được mang HAI dấu '#'.

    Đo thật 23/09/2026: sửa `suy_duong_dan_cong_khai` cho các khối
    include, tôi trả sẵn '/guilde/dcabot#params'. Nhưng bộ cắt theo neo
    còn TỰ NỐI neo của từng phần vào sau, nên ra
    '/guilde/dcabot#params#nhom-dao-dong' — 31 đoạn hỏng, rải trên 8
    đường dẫn khác nhau (ca này đếm theo ĐƯỜNG DẪN riêng biệt, nên báo
    8 chứ không phải 31).

    Vì sao bộ kiểm đường dẫn trong chay_test.py KHÔNG bắt được: nó cắt ở
    dấu '#' ĐẦU TIÊN rồi mới đối chiếu route, nên phần rác phía sau lọt
    qua im lặng — route vẫn hợp lệ, link vẫn 200, chỉ là trình duyệt
    không cuộn tới đâu cả. Phải kiểm riêng ở đây.

    ĐỐI CHỨNG ÂM: đổi hàm đó trả '/guilde/dcabot#params' thì ca này
    PHẢI trượt với 31 đường dẫn.
    """
    hong = sorted({d.duong_dan for d in cac_doan if d.duong_dan.count('#') > 1})
    ok = not hong
    if ok:
        chi_tiet = '%d đoạn, không đường dẫn nào có quá một dấu #' % len(cac_doan)
    else:
        chi_tiet = ('%d ĐƯỜNG DẪN CÓ HAI DẤU # — gần như chắc chắn là gắn sẵn '
                    'neo trong suy_duong_dan_cong_khai rồi lại bị bộ cắt nối '
                    'thêm neo:\n' % len(hong)
                    + '\n'.join('    ' + p for p in hong[:8]))
    return ok, chi_tiet


def ca_7_khong_co_neo_rong(cac_doan):
    import collections
    dai_nhat = collections.defaultdict(int)
    for d in cac_doan:
        if '#' in d.duong_dan:
            dai_nhat[d.duong_dan] = max(dai_nhat[d.duong_dan], len(d.van_ban_vi))

    rong = {k: v for k, v in dai_nhat.items() if v < NGUONG_KY_TU}
    moi = {k: v for k, v in rong.items() if k not in NEO_RONG_DA_BIET}

    ok = not moi
    if ok:
        chi_tiet = ('%d neo, %d rỗng — đều nằm trong danh sách đã truy ra lý do'
                    % (len(dai_nhat), len(rong)))
    else:
        chi_tiet = ('%d NEO RỖNG MỚI (dưới %d ký tự) — nhiều khả năng id đang đặt '
                    'trên chính thẻ tiêu đề thay vì thẻ bao:\n' % (len(moi), NGUONG_KY_TU)
                    + '\n'.join('    %4d ký tự  %s' % (v, k) for k, v in sorted(moi.items())))
    return ok, chi_tiet

# =====================================================================
# CHẠY
# =====================================================================

def chay() -> int:
    print('=' * 78)
    print('THU_CAT_NEO — kiểm cắt đoạn theo neo, chạy trên chi_muc.sqlite THẬT')
    print('=' * 78)

    cac_doan = _doc_chi_muc_that()
    if cac_doan is None:
        print()
        print('CHƯA CÓ chi_muc.sqlite — dựng trước bằng:  python kho/chi_muc.py')
        print('(Các ca 1-5 cần chỉ mục thật nên BỎ QUA hết; ca 6 (tự kiểm doan_kieu)')
        print(' không cần chỉ mục nên vẫn chạy.)')
        print()
        dat6, chi_tiet6 = ca_6_tu_kiem_doan_kieu()
        _in_dong('6', 'tu_kiem_doan_kieu', dat6, chi_tiet6)
        return 0 if dat6 else 1

    cac_doan_trang = [d for d in cac_doan if d.duong_dan == TRANG_KIEM
                       or d.duong_dan.startswith(TRANG_KIEM + '#')]
    tong_hien_tai = len(cac_doan)

    ca_bang = []

    dat1, ct1 = ca_1_it_nhat_9_doan(cac_doan_trang)
    ca_bang.append(('1', f'{TRANG_KIEM}: >= 9 đoạn', dat1, ct1))

    dat2, ct2 = ca_2_tieu_de_khac_nhau(cac_doan_trang)
    ca_bang.append(('2', f'{TRANG_KIEM}: >= 6 tiêu đề khác nhau', dat2, ct2))

    dat3, ct3 = ca_3_troubleshooting_la_loi(cac_doan_trang)
    ca_bang.append(('3', '#troubleshooting tồn tại và kieu==loi', dat3, ct3))

    dat4, ct4 = ca_4_buoc_co_step(cac_doan_trang)
    ca_bang.append(('4', 'có đoạn kieu==buoc với neo #step-', dat4, ct4))

    dat5, ct5 = ca_5_doi_chung_am_khong_giam(tong_hien_tai)
    ca_bang.append(('5', 'ĐỐI CHỨNG ÂM: tổng đoạn không giảm', dat5, ct5))

    dat6, ct6 = ca_6_tu_kiem_doan_kieu()
    ca_bang.append(('6', 'tự kiểm doan_kieu() (8 cặp, 5 kiểu)', dat6, ct6))

    dat7, ct7 = ca_7_khong_co_neo_rong(cac_doan)
    ca_bang.append(('7', 'khong co neo RONG moi (moi rong)', dat7, ct7))

    dat8, ct8 = ca_8_khong_co_hai_dau_thang(cac_doan)
    ca_bang.append(('8', 'khong duong dan nao co HAI dau #', dat8, ct8))

    print()
    for so, ten, dat, chi_tiet in ca_bang:
        _in_dong(so, ten, dat, chi_tiet)

    print()
    print(f'Số đoạn của {TRANG_KIEM} (kể cả các mảnh #neo): {len(cac_doan_trang)}')
    print(f'Tổng số đoạn toàn chỉ mục: {tong_hien_tai}  (trước sửa: {TONG_DOAN_TRUOC})')

    print()
    print('--- 10 dòng mẫu của %s sau khi cắt lại ---' % TRANG_KIEM)
    print(f'{"ma":40} | {"kieu":10} | {"tieu_de":45} | duong_dan')
    for d in cac_doan_trang[:10]:
        print(f'{d.ma:40} | {(d.kieu or ""):10} | {d.tieu_de[:45]:45} | {d.duong_dan}')

    so_truot = sum(1 for _, _, dat, _ in ca_bang if not dat)
    print()
    print(f'SỐ CA TRƯỢT: {so_truot}/{len(ca_bang)}')
    return 0 if so_truot == 0 else 1


def _in_dong(so, ten, dat, chi_tiet):
    trang_thai = ' OK ' if dat else 'TRUOT'
    print(f'[{trang_thai}] ca {so}: {ten}')
    for dong in str(chi_tiet).splitlines():
        print(f'          {dong}')


if __name__ == '__main__':
    sys.exit(chay())
