# -*- coding: utf-8 -*-
"""Chấm bộ test: định tuyến có đúng ý định không, tìm kiếm có ra đúng tài liệu không.

Chạy:  python thu/chay_test.py          (đứng ở thư mục chatbot)
       python thu/chay_test.py -v       (in cả các đoạn tìm được)

Hai phần chấm độc lập nhau, vì chúng hỏng vì những lý do khác nhau:

  ĐỊNH TUYẾN  — chạy được ngay, không cần chỉ mục, không cần CSDL.
                Hỏng nghĩa là bảng từ khoá thiếu hoặc thứ tự ưu tiên sai.
  TÌM KIẾM    — cần chi_muc.sqlite. Hỏng nghĩa là cắt đoạn hoặc gán nhãn sai.

Không có chỉ mục thì phần tìm kiếm tự bỏ qua kèm lời nhắc, chứ KHÔNG nổ
lỗi — để người mới kéo mã về vẫn chạy được một nửa bộ test ngay lập tức.

Mã thoát: 0 nếu mọi phép chấm ĐÃ CHẠY đều đạt, 1 nếu có cái trượt.
"""
import io
import json
import os
import sys
import unicodedata

GOC = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if GOC not in sys.path:
    sys.path.insert(0, GOC)

CHI_TIET = '-v' in sys.argv


def bo_dau(s):
    """Bỏ dấu tiếng Việt để so khớp nới tay.

    Dùng cho CHẤM ĐIỂM thôi, không dùng cho tìm kiếm: khách gõ 'cai dat'
    mà tài liệu viết 'cài đặt' thì vẫn nên tính là khớp, nhưng bản thân
    bộ tìm kiếm thì phải giữ dấu (xem chú thích trong tim/bm25.py).
    """
    s = unicodedata.normalize('NFD', s.lower())
    s = u''.join(c for c in s if unicodedata.category(c) != 'Mn')
    return s.replace(u'đ', 'd')


def nap_bo_test():
    p = os.path.join(GOC, 'thu', 'bo_test.json')
    return json.load(io.open(p, encoding='utf-8'))['cau_hoi']


# Ba ý định này DẪN TỚI CÙNG MỘT NHÁNH trong nao/tra_loi.py: tìm kiếm rồi
# diễn giải. Nhầm lẫn giữa chúng không gây hậu quả gì cho khách, nên chấm
# là trượt thì chỉ tạo ra tiếng ồn che mất lỗi thật.
#
# Còn nhầm sang `gia`, `tra_key`, `cam_ket`, `chao` thì CÓ hậu quả: hoặc bot
# im khi đáng lẽ phải trả lời, hoặc bot trả lời khi đáng lẽ phải chuyển
# người. Bốn nhãn đó chấm nghiêm.
CUNG_MOT_NHANH = {'huong_dan', 'loi', 'khac'}


def tuong_duong(mong, thuc):
    if mong == thuc:
        return True
    return mong in CUNG_MOT_NHANH and thuc in CUNG_MOT_NHANH


# ---------------------------------------------------------------- định tuyến
def cham_dinh_tuyen(bo):
    try:
        from nao.dinh_tuyen import phan_loai
        from giao_uoc import NguCanh
    except Exception as e:
        print('BO QUA phan dinh tuyen: %s' % e)
        return None

    dat, truot, xap_xi = 0, [], []
    for m in bo:
        if 'y_dinh' not in m:
            continue
        ng = NguCanh(phien='test', ngon_ngu='vi')
        try:
            y, tin = phan_loai(m['hoi'], ng)
        except Exception as e:
            truot.append((m['hoi'], m['y_dinh'], 'LOI: %s' % e))
            continue
        if tuong_duong(m['y_dinh'], y):
            dat += 1
            if y != m['y_dinh']:
                xap_xi.append((m['hoi'], m['y_dinh'], y))
        else:
            truot.append((m['hoi'], m['y_dinh'], y))

    tong = dat + len(truot)
    print('')
    print('DINH TUYEN : %d/%d dat (%.0f%%)' % (dat, tong, 100.0 * dat / tong if tong else 0))
    for hoi, mong, thuc in truot:
        print('   TRUOT  %-46s mong %-11s ra %s' % (hoi[:46], mong, thuc))
    if xap_xi:
        print('   (%d cau lech trong cung mot nhanh — khong anh huong khach)' % len(xap_xi))
    return len(truot) == 0


# ------------------------------------------------------------------ tìm kiếm
def cham_tim_kiem(bo):
    if not os.path.isfile(os.path.join(GOC, 'chi_muc.sqlite')):
        print('')
        print('BO QUA phan tim kiem: chua co chi_muc.sqlite.')
        print('   Dung chi muc truoc bang:  python kho/chi_muc.py')
        return None
    try:
        from tim.lai import lay_bo_tim
        from giao_uoc import LocTim
    except Exception as e:
        print('BO QUA phan tim kiem: %s' % e)
        return None

    bt = lay_bo_tim()
    dat, truot = 0, []
    for m in bo:
        dk = [k for k in ('san_pham', 'loai', 'chua_cum_tu') if k in m]
        if not dk:
            continue
        try:
            kq = bt.tim(m['hoi'], LocTim(ngon_ngu='vi'), so_luong=5)
        except Exception as e:
            truot.append((m['hoi'], 'LOI: %s' % e))
            continue
        if not kq:
            truot.append((m['hoi'], 'khong tim duoc gi'))
            continue

        loi = []
        if 'san_pham' in m:
            co = any(k.doan.san_pham == m['san_pham'] for k in kq)
            if not co:
                loi.append('khong co doan nao thuoc %s (co: %s)'
                           % (m['san_pham'],
                              ','.join(sorted({str(k.doan.san_pham) for k in kq}))))
        if 'loai' in m:
            if not any(k.doan.loai == m['loai'] for k in kq):
                loi.append('khong co doan nao loai %s' % m['loai'])
        if 'chua_cum_tu' in m:
            gop = bo_dau(u' '.join(k.doan.van_ban_vi + u' ' + k.doan.van_ban_en for k in kq))
            if not any(bo_dau(c) in gop for c in m['chua_cum_tu']):
                loi.append('khong doan nao chua mot trong: %s' % ', '.join(m['chua_cum_tu']))

        if loi:
            truot.append((m['hoi'], '; '.join(loi)))
        else:
            dat += 1

        if CHI_TIET:
            print('   %-44s -> %s' % (m['hoi'][:44],
                                      ' | '.join('%s(%.2f)' % (k.doan.ma, k.diem) for k in kq[:3])))

    tong = dat + len(truot)
    print('')
    print('TIM KIEM   : %d/%d dat (%.0f%%)' % (dat, tong, 100.0 * dat / tong if tong else 0))
    for hoi, ly_do in truot:
        print('   TRUOT  %-44s %s' % (hoi[:44], ly_do))
    return len(truot) == 0


# --------------------------------------------- đường dẫn trích nguồn
def cham_duong_dan():
    """Mọi đường dẫn trích nguồn phải là một route CÓ THẬT.

    Vì sao cần hẳn một phép kiểm: đường dẫn được SUY từ tên tệp PHP, mà
    tên tệp không phải lúc nào cũng trùng slug trên web. Đo trên trang
    thật ngày 22/09/2026: 9 trong 38 đường dẫn trả 404 — bot đã đưa
    khách link gãy mà không có gì báo, vì câu trả lời vẫn đúng nội dung.

    Kiểm ở đây đối chiếu với public/.htaccess chứ không gọi mạng: test
    phải chạy được khi không có Internet, và route là thứ khai trong tệp
    nên đối chiếu tệp là đủ chắc.
    """
    import re as _re
    # Thu muc web ten KHAC NHAU o hai noi: `public` tren may phat trien
    # (XAMPP), `public_html` tren may chu that (HestiaCP). Ban dau chi tim
    # `public`, nen chay tren may chu la bo qua im lang — bo kiem duong dan
    # coi nhu khong ton tai o dung noi no can chay nhat. Chinh cai bay
    # `public`/`public_html` nay tung lam chi muc dung ra 0 doan ma van bao
    # "Xong": phai thu CA HAI ten, khong duoc doan mot cai.
    ht = None
    for _ten in ('public', 'public_html'):
        _ung_vien = os.path.normpath(
            os.path.join(GOC, '..', '..', '..', _ten, '.htaccess'))
        if os.path.isfile(_ung_vien):
            ht = _ung_vien
            break
    if ht is None:
        print('')
        print('BO QUA kiem duong dan: khong thay .htaccess trong public/ lan public_html/')
        return None
    if not os.path.isfile(os.path.join(GOC, 'chi_muc.sqlite')):
        print('')
        print('BO QUA kiem duong dan: chua co chi_muc.sqlite')
        return None
    try:
        from kho.chi_muc import doc_tat_ca
    except Exception as e:
        print('BO QUA kiem duong dan: %s' % e)
        return None

    luat = []
    for dong in io.open(ht, encoding='utf-8', errors='replace'):
        m = _re.match(r'\s*RewriteRule\s+\^([^\s$]+)\$?\s', dong)
        if m:
            luat.append(m.group(1))

    def co_route(p):
        # BỎ PHẦN #neo TRƯỚC KHI SO KHỚP. Trình duyệt KHÔNG gửi fragment lên
        # máy chủ — nó chỉ dùng để cuộn tới đúng mục — nên `.htaccess` không
        # bao giờ có luật nào khớp được `/guilde/dcabot#step-1`.
        #
        # Từ 23/09/2026 chỉ mục cắt theo neo nên `duong_dan` mang theo `#`.
        # Không bỏ fragment thì 72 route mới đều báo TRƯỢT dù link hoàn toàn
        # đúng — bộ kiểm tụt từ 36/36 xuống 36/108 mà chẳng có gì hỏng thật.
        goc = p.split('#', 1)[0].lstrip('/')
        for l in luat:
            try:
                if _re.fullmatch(l, goc):
                    return True
            except _re.error:
                if l == goc:
                    return True
        return False

    ds = sorted({d.duong_dan for d in doc_tat_ca()})
    hong = [p for p in ds if not co_route(p)]
    print('')
    print('DUONG DAN : %d/%d la route co that' % (len(ds) - len(hong), len(ds)))
    for p in hong:
        print('   TRUOT  %s  — khong khop RewriteRule nao trong public/.htaccess' % p)
    return not hong


def main():
    bo = nap_bo_test()
    print('Bo test: %d cau' % len(bo))
    a = cham_dinh_tuyen(bo)
    b = cham_tim_kiem(bo)
    c = cham_duong_dan()
    print('')
    ket = [x for x in (a, b, c) if x is not None]
    if not ket:
        print('KHONG CHAM DUOC GI — xem cac dong BO QUA o tren.')
        return 1
    return 0 if all(ket) else 1


if __name__ == '__main__':
    sys.exit(main())
