# -*- coding: utf-8 -*-
"""CAU_HINH_AI.PY — mô hình AI theo VAI TRÒ + giá + ngân sách, đọc từ CSDL.

VAI TRÒ (chủ dự án 24/09/2026)
------------------------------
- tra_loi     tuyến đầu, phần lớn tin nhắn — mô hình rẻ, nhanh.
- chuyen_gia  tư vấn đặt code bot khi khách đã tả logic / hỏi giá chiến lược
              cụ thể — mô hình tư duy cao (nao/tra_loi.py::_can_chuyen_gia).
- tong_hop    tóm gọn giữa phiên, tóm tắt cuối phiên, hồ sơ khách — chạy nền,
              là "trí nhớ" mô hình rẻ dựa vào, nên dùng mô hình tư duy cao.
- vat         việc vặt: chia nhóm tin nhắn dồn — mô hình rẻ.
- du_phong    khi mô hình chính hỏng hạ tầng (timeout, 403, 5xx). Hai ô: cho mô hình
              rẻ và cho mô hình mạnh — nên KHÁC HÃNG với mô hình chính (một hãng
              sự cố thì hãng kia vẫn chạy; đo 24/09: haiku 40s, gpt-6-sol 11s).

Sửa trong Cài đặt website -> tab "Chatbot AI" (bảng `chat_cau_hinh`). Đọc
lại mỗi THOI_GIAN_DEM giây. CSDL hỏng -> giá trị mặc định / biến môi trường,
không bao giờ ném lỗi ra lõi.

Tự kiểm:  python congcu/cau_hinh_ai.py
"""
from __future__ import annotations

import logging
import os
import re
import sys
import threading
import time
from typing import Any, Callable, Dict, Optional

try:
    from congcu import csdl
except ImportError:
    sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    from congcu import csdl  # type: ignore

_log = logging.getLogger('chatbot.congcu.cau_hinh_ai')

THOI_GIAN_DEM = 60
TY_GIA_VND_USD = 26000.0      # chỉ để quy ngân sách/giá VNĐ về USD cho bộ đếm cũ

MAC_DINH: Dict[str, str] = {
    'mo_hinh_tra_loi': '',        # rỗng -> LLM_MODEL_DIEN_GIAI trong .env
    'mo_hinh_chuyen_gia': '',     # rỗng -> dùng mô hình trả lời
    'mo_hinh_tong_hop': '',       # rỗng -> LLM_MODEL_TOM_TAT / mô hình trả lời
    'mo_hinh_vat': '',
    'mo_hinh_du_phong': '',       # rỗng -> LLM_MODEL_DU_PHONG — dự phòng cho vai mô hình rẻ
    'mo_hinh_du_phong_manh': '',  # dự phòng cho vai mô hình mạnh (khác hãng với mô hình chính)
    'bat_chuyen_gia': '1',
    'gia_re_vnd_1m': '500',
    'gia_manh_vnd_1m': '700',
    'ngan_sach_ngay_vnd': '0',    # 0 = không giới hạn
}

# Tên mô hình "tư duy cao" — để tính giá và nới thời gian chờ.
_MAU_MANH = re.compile(r'(opus|sonnet|fable|gpt-[5-9]|grok|gemini-[\d.]+-pro|-pro\b)', re.I)

_dem: Dict[str, Any] = {}
_khoa = threading.Lock()


def _doc_csdl(khong_gian: str) -> Dict[str, str]:
    conn = csdl.ket_noi()
    if conn is None:
        return {}
    with conn:
        with conn.cursor() as ct:
            ct.execute('SELECT khoa, gia_tri FROM chat_cau_hinh WHERE khong_gian = %s', (khong_gian,))
            return {str(r['khoa']): str(r['gia_tri'] or '') for r in (ct.fetchall() or [])}


def doc(khong_gian: str = 'tradingauto', _nguon: Optional[Callable[[str], Dict[str, str]]] = None) -> Dict[str, str]:
    kg = khong_gian or 'tradingauto'
    with _khoa:
        muc = _dem.get(kg)
        if muc and time.time() - muc[0] < THOI_GIAN_DEM and _nguon is None:
            return muc[1]
    kq = dict(MAC_DINH)
    try:
        tu_csdl = (_nguon or _doc_csdl)(kg)
        kq.update({k: v for k, v in tu_csdl.items() if v is not None})
        if tu_csdl:
            kq['_tu_csdl'] = '1'
    except Exception as loi:  # noqa: BLE001 - cấu hình hỏng không được chặn câu trả lời
        _log.warning('Không đọc được chat_cau_hinh (%s) — dùng mặc định.', loi)
        if muc:
            return muc[1]
    with _khoa:
        _dem[kg] = (time.time(), kq)
    return kq


def mo_hinh(vai: str, khong_gian: str = 'tradingauto') -> str:
    """Tên mô hình cho vai. Rỗng nếu không cấu hình (nơi gọi tự dùng mặc định .env)."""
    ch = doc(khong_gian)
    ten = (ch.get(f'mo_hinh_{vai}') or '').strip()
    if not ten and vai == 'chuyen_gia':
        ten = (ch.get('mo_hinh_tra_loi') or '').strip()
    if not ten and vai in ('tong_hop', 'vat'):
        ten = (os.environ.get('LLM_MODEL_TOM_TAT', '') if vai == 'tong_hop' else '').strip() \
            or (ch.get('mo_hinh_tra_loi') or '').strip()
    return ten


def che_do_tu_van(khong_gian: str = 'tradingauto') -> str:
    """'agent' (nao/agent.py — mục tiêu phiên + công cụ + đọc lại, 29/09/2026) hoặc 'cu' (nhánh rẽ cũ).
    Mặc định 'cu' cho tới khi agent thắng chấm mù (chủ dự án chấm) — bật bằng khoá che_do_tu_van=agent."""
    return 'agent' if (doc(khong_gian).get('che_do_tu_van') or '').strip() == 'agent' else 'cu'


def bat_chuyen_gia(khong_gian: str = 'tradingauto') -> bool:
    return (doc(khong_gian).get('bat_chuyen_gia') or '1').strip() not in ('0', '', 'off')


def la_mo_hinh_manh(ten: str) -> bool:
    return bool(_MAU_MANH.search(ten or ''))


# Mô hình rẻ nhưng NGHĨ NGẦM trước khi trả lời (DeepSeek 4.1 Flash: trung vị 35s/lượt agent, đo 29/09/2026)
# -> cần thời gian chờ như mô hình mạnh, không tính giá như mô hình mạnh.
_MAU_NGHI_LAU = re.compile(r'(deepseek-v4\.1|deepseek-v4-pro|reason|think)', re.I)


def can_cho_lau(ten: str) -> bool:
    return la_mo_hinh_manh(ten) or bool(_MAU_NGHI_LAU.search(ten or ''))


def _so(gia_tri: str, mac_dinh: float) -> float:
    try:
        return float(str(gia_tri).replace(',', '.'))
    except (TypeError, ValueError):
        return mac_dinh


def gia_usd_tren_trieu(ten_mo_hinh: str) -> float:
    """Giá cổng AI (USD / 1 triệu token, vào và ra như nhau) theo cài đặt."""
    ch = doc()
    vnd = _so(ch.get('gia_manh_vnd_1m' if la_mo_hinh_manh(ten_mo_hinh) else 'gia_re_vnd_1m'), 500.0)
    return vnd / TY_GIA_VND_USD


def co_cai_dat() -> bool:
    """Đã đọc được bảng chat_cau_hinh (có dòng) chưa — chưa thì nơi gọi dùng .env như cũ."""
    return doc().get('_tu_csdl') == '1'


def tran_ngay_usd() -> Optional[float]:
    """Trần chi phí ngày (USD). None = không giới hạn (ngân sách 0 hoặc để trống)."""
    vnd = _so(doc().get('ngan_sach_ngay_vnd') or '0', 0.0)
    return None if vnd <= 0 else vnd / TY_GIA_VND_USD


if __name__ == '__main__':
    gia = {'mo_hinh_tra_loi': 'deepseek-v4-flash', 'mo_hinh_tong_hop': 'claude-opus-5',
           'mo_hinh_chuyen_gia': '', 'gia_manh_vnd_1m': '700', 'ngan_sach_ngay_vnd': '0'}
    ch = doc('thu', _nguon=lambda kg: gia)
    assert ch['mo_hinh_tong_hop'] == 'claude-opus-5' and ch['bat_chuyen_gia'] == '1'
    _dem['tradingauto'] = (time.time(), ch)
    assert mo_hinh('tong_hop') == 'claude-opus-5'
    assert mo_hinh('chuyen_gia') == 'deepseek-v4-flash', 'trống thì dùng mô hình trả lời'
    assert la_mo_hinh_manh('claude-opus-5') and not la_mo_hinh_manh('deepseek-v4-flash')
    assert abs(gia_usd_tren_trieu('claude-opus-5') - 700 / TY_GIA_VND_USD) < 1e-9
    assert tran_ngay_usd() is None and co_cai_dat()
    ch['ngan_sach_ngay_vnd'] = '52000'
    assert abs(tran_ngay_usd() - 2.0) < 1e-9
    assert doc('hong', _nguon=lambda kg: 1 / 0)['mo_hinh_tra_loi'] == ''   # CSDL hỏng -> mặc định
    print('TỰ KIỂM congcu/cau_hinh_ai.py QUA.')
