# -*- coding: utf-8 -*-
"""HANG_DOI.PY — ghi lại câu khách hỏi mà TẦNG 1 (nao/tang_mot.py) KHÔNG
khớp được, để chủ website vào phần quản trị viết câu trả lời soạn sẵn.

VÌ SAO FILE NÀY TỒN TẠI
------------------------
Đây chính là vòng lặp làm tầng 1 dày lên theo thời gian: bot ghi lại những
câu chưa có câu trả lời soạn sẵn (bảng `chat_thieu`), chủ website xem danh
sách này trong quản trị, viết câu trả lời rồi duyệt — lần sau câu tương tự
sẽ được tầng 1 khớp và trả lời ngay, không cần AI nữa. Mục tiêu dài hạn:
tỉ lệ phủ tầng 1 tăng dần, hoá đơn AI giảm dần (xem đầu `nao/tang_mot.py`).

RIÊNG TƯ — ĐỌC KỸ TRƯỚC KHI SỬA
----------------------------------
Bảng `chat_thieu` KHÔNG lưu tên đăng nhập, session id, IP hay bất kỳ định
danh nào của khách. Hàm `ghi_nhan()` bên dưới CHỦ Ý không nhận tham số
`nguoi_dung`/`phien` — chỉ lưu NỘI DUNG câu hỏi và các trường thống kê đi
kèm (ý định, sản phẩm đoán được, câu bot đã trả lời, số lần hỏi). ĐỪNG
thêm cột nào khác vào bảng này để lưu danh tính khách.

Cách chạy tự kiểm (không cần CSDL):
    python congcu/hang_doi.py
"""
from __future__ import annotations

import logging
import os
import re
import sys
from typing import Optional

try:
    from . import csdl
except ImportError:
    # Chạy trực tiếp `python congcu/hang_doi.py` thì đây không phải một
    # package con — thêm cả thư mục cha (gốc chatbot) lẫn thư mục của
    # chính tệp này vào sys.path, y hệt cách congcu/cho_trong.py đã làm
    # (lý do chi tiết xem chú thích đầu tệp đó).
    _THU_MUC_TOI = os.path.dirname(os.path.abspath(__file__))
    _THU_MUC_CHA = os.path.dirname(_THU_MUC_TOI)
    for _d in (_THU_MUC_CHA, _THU_MUC_TOI):
        if _d not in sys.path:
            sys.path.insert(0, _d)
    import csdl  # type: ignore

_log = logging.getLogger('chatbot.congcu.hang_doi')


# =====================================================================
# CHUẨN HOÁ ĐỂ GOM — GIỮ NGUYÊN DẤU tiếng Việt (bẫy đã dặn ở nao/tang_mot.py:
# bỏ dấu 'bớt' thành 'bot' là đụng đúng chữ khách dùng gọi sản phẩm).
# =====================================================================

_RE_KHOANG_TRANG = re.compile(r'\s+')
# Dấu câu ASCII thường gặp + vài dấu câu Unicode hay xuất hiện khi khách
# gõ trên điện thoại (dấu ba chấm, ngoặc kép cong, ngoặc kép Pháp).
_RE_DAU_CAU = re.compile(r'[!"#$%&\'()*+,\-./:;<=>?@\[\\\]^_`{|}~…“”‘’«»]')

# Cột `cau_hoi_gom` là VARCHAR(190) có UNIQUE KEY — dài hơn là MySQL báo
# lỗi hoặc cắt ngầm (tuỳ chế độ SQL), nên PHẢI cắt ở đây, không dựa vào DB.
DO_DAI_TOI_DA_CAU_HOI_GOM = 190
# Cột `cau_hoi` (nội dung gốc, để chủ website đọc) — cắt rộng rãi hơn.
DO_DAI_TOI_DA_CAU_HOI = 500


def gom(cau_hoi: str) -> str:
    """Dạng chuẩn hoá dùng để gom câu giống nhau: hạ chữ thường, bỏ dấu
    câu, gộp khoảng trắng, CẮT CÒN TỐI ĐA 190 KÝ TỰ. GIỮ NGUYÊN dấu tiếng
    Việt — chỉ chuẩn hoá chữ thường/khoảng trắng/dấu câu, không đụng dấu
    thanh điệu."""
    if not cau_hoi:
        return ''
    sach = cau_hoi.strip().lower()
    sach = _RE_DAU_CAU.sub(' ', sach)
    sach = _RE_KHOANG_TRANG.sub(' ', sach).strip()
    return sach[:DO_DAI_TOI_DA_CAU_HOI_GOM]


# =====================================================================
# GHI NHẬN
# =====================================================================

def ghi_nhan(
    cau_hoi: str,
    y_dinh: Optional[str],
    san_pham: Optional[str],
    tra_loi_may: str,
    khong_gian: str = 'tradingauto',
) -> None:
    """Ghi/cộng dồn một câu khách hỏi mà tầng 1 không khớp được.

    INSERT mới nếu (khong_gian, cau_hoi_gom) chưa từng thấy, ngược lại
    ON DUPLICATE KEY UPDATE: so_lan += 1, lan_cuoi = NOW(), tra_loi_may
    được thay bằng câu mới nhất (làm bản nháp cập nhật cho chủ website).
    KHÔNG đụng `trang_thai`/`muc_id` khi cập nhật — chủ đã đánh dấu
    'da_viet'/'bo_qua' cho câu này rồi thì không được bị INSERT sau ghi
    đè ngược lại về 'moi'.

    MỌI lỗi (thiếu CSDL, thiếu bảng, mất kết nối giữa chừng...) đều bị
    NUỐT ở đây, chỉ `logging.warning` — ghi hàng đợi hỏng TUYỆT ĐỐI không
    được làm hỏng câu trả lời đã gửi cho khách.
    """
    try:
        cau_hoi_sach = (cau_hoi or '').strip()
        if not cau_hoi_sach:
            return

        cau_hoi_gom = gom(cau_hoi_sach)
        if not cau_hoi_gom:
            return

        cau_hoi_luu = cau_hoi_sach[:DO_DAI_TOI_DA_CAU_HOI]
        tra_loi_may_luu = (tra_loi_may or '')[:DO_DAI_TOI_DA_CAU_HOI * 4]

        conn = csdl.ket_noi()
        if conn is None:
            return
        try:
            with conn:
                with conn.cursor() as con_tro:
                    con_tro.execute(
                        """
                        INSERT INTO chat_thieu
                            (khong_gian, cau_hoi, cau_hoi_gom, so_lan,
                             y_dinh, san_pham, tra_loi_may,
                             lan_dau, lan_cuoi, trang_thai)
                        VALUES
                            (%s, %s, %s, 1, %s, %s, %s, NOW(), NOW(), 'moi')
                        ON DUPLICATE KEY UPDATE
                            so_lan = so_lan + 1,
                            lan_cuoi = NOW(),
                            tra_loi_may = VALUES(tra_loi_may)
                        """,
                        (khong_gian, cau_hoi_luu, cau_hoi_gom, y_dinh or None,
                         san_pham or None, tra_loi_may_luu),
                    )
                conn.commit()
        finally:
            try:
                conn.close()
            except Exception:  # noqa: BLE001 - `with conn:` ở trên thường đã đóng, phòng thêm không hại gì
                pass
    except Exception as loi:  # noqa: BLE001 - LƯỚI AN TOÀN CUỐI CÙNG, xem lời dặn ở docstring
        _log.warning('Lỗi khi ghi nhận câu hỏi thiếu vào chat_thieu: %s', loi)


# =====================================================================
# TỰ KIỂM — không cần CSDL thật
# =====================================================================
if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')

    print('=' * 78)
    print('1) TỰ KIỂM gom() — hai cách viết khác nhau phải ra CÙNG một chuỗi gom')
    print('=' * 78)

    a = gom('  DCA Bot   Giá Bao Nhiêu ?  ')
    b = gom('dca bot giá bao nhiêu?')
    print(f"  gom(A) = {a!r}")
    print(f"  gom(B) = {b!r}")
    assert a == b, 'hai cách viết (hoa/thường, khoảng trắng, dấu chấm hỏi) phải gom về cùng 1 chuỗi'
    print('  KHẲNG ĐỊNH: hai chuỗi gom giống hệt nhau — QUA.')
    print()

    print('=' * 78)
    print('2) TỰ KIỂM gom() — câu dài 400 ký tự phải bị cắt còn <= 190')
    print('=' * 78)
    cau_dai = ('cài đặt dcabot vào mt5 như thế nào ' * 12)[:400]  # đúng 400 ký tự
    print(f"  độ dài câu gốc: {len(cau_dai)}")
    ket_qua_dai = gom(cau_dai)
    print(f"  độ dài sau gom(): {len(ket_qua_dai)}")
    assert len(cau_dai) == 400
    assert len(ket_qua_dai) <= DO_DAI_TOI_DA_CAU_HOI_GOM
    print(f'  KHẲNG ĐỊNH: {len(ket_qua_dai)} <= {DO_DAI_TOI_DA_CAU_HOI_GOM} — QUA.')
    print()

    print('=' * 78)
    print('3) TỰ KIỂM gom() — GIỮ NGUYÊN dấu tiếng Việt')
    print('=' * 78)
    co_dau = gom('Anh/chị cho em hỏi Bớt được không ạ?')
    print(f"  gom('...Bớt được không ạ?') = {co_dau!r}")
    assert 'bớt' in co_dau, 'PHẢI giữ dấu — "bớt" bỏ dấu thành "bot" sẽ đụng chữ khách gọi sản phẩm'
    assert 'bot' not in co_dau.replace('bớt', ''), 'không được lẫn "bot" do bỏ dấu "bớt"'
    print('  KHẲNG ĐỊNH: dấu tiếng Việt được giữ nguyên trong chuỗi gom — QUA.')
    print()

    print('=' * 78)
    print('4) TỰ KIỂM ghi_nhan() khi KHÔNG có CSDL — không được sập')
    print('=' * 78)
    ghi_nhan('cài dcabot vào mt5 sao', 'huong_dan', 'dcabot', 'Anh/chị tải EA rồi...')
    ghi_nhan('', 'huong_dan', None, 'câu rỗng, phải bị bỏ qua sớm trước khi chạm CSDL')
    print('  ghi_nhan() chạy xong (cả câu bình thường lẫn câu rỗng), không ném lỗi ra ngoài.')
    print()

    print('TỰ KIỂM hang_doi.py HOÀN TẤT — TẤT CẢ ĐÚNG NHƯ KỲ VỌNG.')
