# -*- coding: utf-8 -*-
"""CÔNG CỤ CỦA AGENT TƯ VẤN (29/09/2026) — nao/agent.py.

Chủ dự án 28/09/2026: bot lệch ngữ cảnh (khách đặt code bot DCA riêng -> bot tư vấn SMART DCA, hai lần báo
giá thuê SMART DCA). Gốc lỗi: tài liệu NHỒI SẴN theo từ khoá câu hiện tại + nhánh giá tất định chen ngang.
Agent thay cách đó: mô hình đọc cả phiên rồi TỰ CHỌN tra gì qua các công cụ dưới đây. Mọi con số (giá, số
liệu VIP) đi ra từ công cụ — đọc CSDL / API thật, mô hình không tự nghĩ.

Mỗi lượt có một `PhienCongCu`: gom các đoạn tài liệu đã tra (đánh số liên tục để mô hình khai
NGUON_DA_DUNG), ghi chú phiên mô hình ghi, và yêu cầu mở phiếu cho admin.
"""
from __future__ import annotations

import json
import logging
import os
import sys
import time
from typing import Any, Dict, List, Optional

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from giao_uoc import SAN_PHAM, LocTim, NguCanh  # noqa: E402

_log = logging.getLogger('chatbot.nao.cong_cu')

# Câu chuẩn của các mục kho tư vấn chủ đã duyệt cho dịch vụ đặt code (chat_muc.cau_hoi_chuan).
MUC_DICH_VU_CODE = {
    'gia': 'chi phí code chiến lược thành bot bao nhiêu',
    'thoi_gian': 'code bot mất bao lâu',
    'quy_trinh': 'quy trình đặt code bot và thanh toán thế nào',
    'nen_tang': 'có code bot cho crypto, phái sinh, cổ phiếu không',
    'ban_giao': 'bộ code bàn giao cài thế nào',
}

LY_DO_ADMIN = ('chot_don', 'gap_nguoi', 'loi', 'tien')

DINH_NGHIA = [
    {'type': 'function', 'function': {
        'name': 'tra_tai_lieu',
        'description': ('Tìm trong tài liệu website và kho tư vấn chủ đã duyệt (LỜI CỦA CHỦ WEBSITE). Dùng cho '
                        'câu hỏi về cách dùng, cài đặt, lỗi, tính năng, chính sách. Viết truy_van là CÂU HỎI THẬT '
                        'khách cần (đã hiểu theo mục tiêu phiên), không chỉ chép từ khoá. Chỉ đặt san_pham khi '
                        'chắc khách đang hỏi đúng sản phẩm đó — đang bàn ĐẶT CODE thì dùng codebot.'),
        'parameters': {'type': 'object', 'properties': {
            'truy_van': {'type': 'string'},
            'san_pham': {'type': 'string', 'enum': list(SAN_PHAM)},
        }, 'required': ['truy_van']}}},
    {'type': 'function', 'function': {
        'name': 'dich_vu_code',
        'description': ('Lời chủ website (đã duyệt) về DỊCH VỤ ĐẶT CODE bot theo chiến lược riêng: bảng khoảng '
                        'giá theo độ phức tạp, thời gian làm, quy trình 6 bước + thanh toán 30/70 + mã nguồn '
                        '+ bảo hành, nền tảng nhận/không nhận, cách cài bộ bàn giao.'),
        'parameters': {'type': 'object', 'properties': {
            'chu_de': {'type': 'string', 'enum': list(MUC_DICH_VU_CODE)},
        }, 'required': ['chu_de']}}},
    {'type': 'function', 'function': {
        'name': 'bang_gia_san_pham',
        'description': ('Bảng giá THUÊ/MUA các sản phẩm có sẵn (bảng product của website — SMART DCA, Copytrade, '
                        'TradingView Bot, Trade Panel, Signal Copier/Broadcaster, MT5 Manager, VipTrend). KHÔNG '
                        'dùng cho giá đặt code riêng (dùng dich_vu_code). Bỏ trống san_pham = cả bảng.'),
        'parameters': {'type': 'object', 'properties': {
            'san_pham': {'type': 'string', 'description': 'mã (dcabot, viptrend, copytrade...) hoặc tên sản phẩm'},
        }}}},
    {'type': 'function', 'function': {
        'name': 'so_lieu_viptrend',
        'description': ('Số liệu thật từng con VipTrend (lệnh, tỉ lệ thắng, PF, lợi nhuận, sụt vốn, vốn CL, khung, '
                        'giá thuê) + link lịch sử từng lệnh, bảng lệnh realtime, trang mua. Tự cập nhật theo lệnh mới.'),
        'parameters': {'type': 'object', 'properties': {
            'con': {'type': 'string', 'description': 'vd "VIP 4", "6B"; bỏ trống = tất cả'},
        }}}},
    {'type': 'function', 'function': {
        'name': 'ghi_chu_phien',
        'description': ('Ghi / cập nhật GHI CHÚ PHIÊN (khách không thấy) — như sổ tay của người tư vấn: mục tiêu '
                        'của khách, điều đã chốt, điều còn thiếu. GỌI khi mục tiêu vừa rõ, khi khách chốt/đổi một '
                        'điều, hoặc khi khách NÓI RÕ chuyển sang việc khác. Ghi đủ trạng thái mới (thay bản cũ).'),
        'parameters': {'type': 'object', 'properties': {
            'loai': {'type': 'string', 'enum': ['dat_code', 'san_pham', 'ho_tro', 'thanh_toan', 'tim_hieu']},
            'muc_tieu': {'type': 'string', 'description': 'một câu: khách đang muốn đạt điều gì'},
            'san_pham': {'type': 'string', 'description': 'sản phẩm có sẵn đang bàn (nếu loai=san_pham/ho_tro)'},
            'da_chot': {'type': 'array', 'items': {'type': 'string'}},
            'con_thieu': {'type': 'array', 'items': {'type': 'string'}},
        }, 'required': ['loai', 'muc_tieu']}}},
    {'type': 'function', 'function': {
        'name': 'bao_admin',
        'description': ('Mở phiếu cho admin (người thật) xử lý: chot_don = khách đã đủ đặc tả / muốn chốt đặt '
                        'code hoặc mua -> admin báo giá cuối; gap_nguoi = khách xin gặp người; loi = sự cố kỹ '
                        'thuật bot không xử lý được; tien = nạp tiền/hoàn tiền/chưa nhận credit.'),
        'parameters': {'type': 'object', 'properties': {
            'ly_do': {'type': 'string', 'enum': list(LY_DO_ADMIN)},
            'tom_tat': {'type': 'string', 'description': 'tóm tắt cho admin: khách cần gì, đã chốt gì'},
        }, 'required': ['ly_do', 'tom_tat']}}},
]


class PhienCongCu:
    """Trạng thái công cụ của MỘT lượt trả lời."""

    def __init__(self, ngu_canh: NguCanh) -> None:
        self.nc = ngu_canh
        self.doan: List[Any] = []            # KetQuaTim theo thứ tự đánh số (1..n)
        self.ly_do_phieu = ''
        self.tom_tat_phieu = ''
        self.nhat_ky: List[str] = []         # tên công cụ + tham số (soi lỗi, ghi chú nhật ký)
        self.so_lieu: List[str] = []         # kết quả số/giá thô — lượt đọc lại đối chiếu

    # ---- điều phối ----
    def goi(self, ten: str, tham_so_json: str) -> str:
        try:
            ts = json.loads(tham_so_json or '{}') or {}
        except ValueError:
            ts = {}
        self.nhat_ky.append(f"{ten}({json.dumps(ts, ensure_ascii=False)[:200]})")
        ham = getattr(self, '_ct_' + ten, None)
        if ham is None:
            return f'(không có công cụ {ten})'
        try:
            return ham(**{k: v for k, v in ts.items() if isinstance(k, str)})
        except TypeError as loi:
            return f'(tham số sai cho {ten}: {loi})'
        except Exception as loi:  # noqa: BLE001 - công cụ hỏng thì mô hình biết và trả lời thận trọng
            _log.exception('Công cụ %s lỗi', ten)
            return f'(công cụ {ten} đang lỗi: {loi} — đừng nêu số liệu/giá mà công cụ này lẽ ra cung cấp)'

    def _them_doan(self, kq: Any) -> int:
        for i, k in enumerate(self.doan, start=1):
            if k.doan.ma == kq.doan.ma:
                return i
        self.doan.append(kq)
        return len(self.doan)

    def _van_ban_doan(self, so: int, kq: Any) -> str:
        d = kq.doan
        nd = d.van_ban_en if (self.nc.ngon_ngu == 'en' and d.van_ban_en) else d.van_ban_vi
        nhan = ' — LỜI CỦA CHỦ WEBSITE, ƯU TIÊN CAO NHẤT' if d.loai == 'tuvan' else ''
        return f"[Đoạn {so}{nhan}] (tiêu đề: {d.tieu_de}, đường dẫn: {d.duong_dan})\n{nd}"

    # ---- công cụ ----
    def _ct_tra_tai_lieu(self, truy_van: str, san_pham: Optional[str] = None) -> str:
        from nao.tra_loi import _doan_tu_muc, khop  # mục kho đọc thẳng CSDL (chỉ mục có thể chưa dựng lại)
        from tim.lai import lay_bo_tim
        ra: List[Any] = []
        try:
            kq = khop(truy_van, self.nc) if khop is not None else None
            if kq is not None and (not san_pham or kq[0].get('san_pham') in (None, '', san_pham)):
                ra.append(_doan_tu_muc(kq[0], str(kq[0].get('tra_loi_vi') or '')))
        except Exception:  # noqa: BLE001
            _log.exception('tra_tai_lieu: khop kho lỗi')
        sp = san_pham if san_pham in SAN_PHAM else None
        bo = lay_bo_tim(khong_gian=self.nc.khong_gian)
        ra += list(bo.tim(truy_van, loc=LocTim(san_pham=sp, ngon_ngu=self.nc.ngon_ngu), so_luong=6) or [])
        if not ra:
            return '(không tìm thấy tài liệu nào — trả lời bằng kiến thức chung nếu là kiến thức chung; thông tin riêng của sản phẩm thì nói chưa chắc)'
        khoi, da = [], set()
        for k in ra:
            if k.doan.ma in da:
                continue
            da.add(k.doan.ma)
            khoi.append(self._van_ban_doan(self._them_doan(k), k))
        return '\n\n'.join(khoi)

    def _ct_dich_vu_code(self, chu_de: str) -> str:
        from nao.tang_mot import nap_muc
        cau = MUC_DICH_VU_CODE.get(chu_de)
        if not cau:
            return '(chủ đề không có)'
        for m in nap_muc(self.nc.khong_gian):
            if (m.get('cau_hoi_chuan') or '').strip() == cau:
                from nao.tra_loi import _doan_tu_muc
                kq = _doan_tu_muc(m, str(m.get('tra_loi_vi') or ''))
                so = self._them_doan(kq)
                self.so_lieu.append(f'dich_vu_code/{chu_de}: ' + str(m.get('tra_loi_vi') or '')[:800])
                return self._van_ban_doan(so, kq)
        return '(chưa có mục kho cho chủ đề này — không nêu số, mời khách mô tả để admin báo)'

    def _ct_bang_gia_san_pham(self, san_pham: Optional[str] = None) -> str:
        from congcu.khuon_mau import cau_bang_gia_tat_ca, cau_gia, cau_gia_them
        from congcu.san_pham import bang_gia_viptrend, danh_sach_san_pham, tim_san_pham
        ng = self.nc.ngon_ngu
        vip = bang_gia_viptrend()
        tk = (san_pham or '').strip().lower()
        if tk and ('vip' in tk or 'trend' in tk):
            if not vip:
                return '(chưa đọc được giá VipTrend)'
            vb = cau_gia_them(vip[0], vip[1], ng)
        elif tk:
            sp = tim_san_pham(tk)
            if sp is None:
                return f'(không có sản phẩm "{san_pham}" trong bảng giá — có thể khách đang nói tới đặt code riêng)'
            vb = cau_gia(sp, ng)
        else:
            vb = cau_bang_gia_tat_ca(danh_sach_san_pham(), ng, [vip[2]] if vip else [])
        self.so_lieu.append('bang_gia_san_pham: ' + vb[:1500])
        return 'BẢNG GIÁ HỆ THỐNG (chép đúng số):\n' + vb

    def _ct_so_lieu_viptrend(self, con: Optional[str] = None) -> str:
        import httpx
        import shop
        goc = shop.lay(self.nc.khong_gian).ten_mien or 'https://tradingauto.org'
        d = httpx.get(goc.rstrip('/') + '/api/viptrend.php', timeout=10,
                      headers={'User-Agent': 'tradingauto-chatbot/1.0'}).json()
        ds = d.get('kenh') or []
        tk = ''.join(ch for ch in (con or '').upper() if ch.isalnum()).replace('VIP', '').replace('GOLD', '')
        if tk:
            ds = [k for k in ds if k['trang'].upper().endswith('GOLDVIP' + tk)] or ds
        vb = json.dumps({'don_vi': d.get('don_vi'), 'bang_realtime': d.get('bang_realtime'), 'thue': d.get('thue'),
                         'kenh': ds}, ensure_ascii=False)
        self.so_lieu.append('so_lieu_viptrend: ' + vb[:3000])
        return vb

    def _ct_ghi_chu_phien(self, loai: str, muc_tieu: str, san_pham: str = '', da_chot: Optional[list] = None,
                          con_thieu: Optional[list] = None) -> str:
        st = self.nc.so_tay if isinstance(self.nc.so_tay, dict) else {}
        cu = st.get('muc_tieu') or {}
        st['muc_tieu'] = {'loai': loai, 'muc_tieu': (muc_tieu or '')[:300], 'san_pham': (san_pham or '')[:40],
                          'da_chot': [str(x)[:200] for x in (da_chot or [])][:20],
                          'con_thieu': [str(x)[:200] for x in (con_thieu or [])][:15], 'luc': int(time.time())}
        if cu.get('loai') and cu.get('loai') != loai:
            _log.info('Mục tiêu phiên %s đổi %s -> %s', self.nc.phien, cu.get('loai'), loai)
        # Giữ tương thích luật cũ: đặt code -> chu_de='code' (so_tay, compact, _can_chuyen_gia đọc)
        if loai == 'dat_code':
            st['chu_de'] = 'code'
        elif loai in ('san_pham', 'ho_tro') and san_pham:
            st['chu_de'] = 'san_pham'
            st['dang_hoi'] = san_pham
        self.nc.so_tay = st
        return 'đã ghi'

    def _ct_bao_admin(self, ly_do: str, tom_tat: str) -> str:
        if ly_do not in LY_DO_ADMIN:
            return f'(ly_do phải là một trong {LY_DO_ADMIN})'
        self.ly_do_phieu, self.tom_tat_phieu = ly_do, (tom_tat or '')[:800]
        return ('đã mở phiếu — admin sẽ liên hệ. Chỉ nói với khách "em đã chuyển admin", KHÔNG hẹn giờ, '
                'không nói admin sẽ làm việc cụ thể gì.')
