# -*- coding: utf-8 -*-
"""KIEM_CONG.PY — kiểm cổng AI bán lại THẬT, chạy TAY trước khi triển khai.

VÌ SAO tồn tại: `GET /v1/models` của cổng LIỆT KÊ được một model KHÔNG có
nghĩa là khoá đang cầm ĐƯỢC PHÉP gọi model đó — bảng giá mới là danh sách
thật, và một model bị chặn (đo được hôm 22/09/2026: HTTP 403
'Model "..." không có trong bảng giá hiện hành.') CHỈ lộ ra lúc gọi thật
`POST /v1/chat/completions`, không lộ ra lúc soát danh sách model. Script
này để cái 403 đó lộ ra NGAY TẠI ĐÂY, lúc người triển khai đang đọc
terminal trước khi bật `CHATBOT_NHA_CUNG_CAP=openai`, chứ không phải lúc
khách đang chat thật và bot đột nhiên chuyển người vì model chính bị chặn.

CHẠY:  python thu/kiem_cong.py     (đứng ở thư mục chatbot)

CẢNH BÁO: script này GỌI THẬT tới cổng — TỐN TIỀN THẬT mỗi lần chạy. KHÔNG
đưa vào CI/test tự động, chỉ chạy TAY, có chủ đích, trước khi triển khai.

Đọc cấu hình từ biến môi trường: LLM_GOC, LLM_KHOA, LLM_MODEL_DIEN_GIAI
(bắt buộc), LLM_MODEL_DU_PHONG (tuỳ chọn). KHÔNG viết khoá vào mã, KHÔNG tự
đọc .env — khác `cau_hinh.py` (đọc .env vì đó là tiến trình chạy nền dài
ngày), tệp này là công cụ chạy tay của người triển khai; nạp .env vào môi
trường TRƯỚC khi chạy (`export $(grep -v '^#' private/.env | xargs)` hay
tương đương) nếu muốn dùng giá trị trong .env.
"""
from __future__ import annotations

import os
import sys
import time

GOC = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if GOC not in sys.path:
    sys.path.insert(0, GOC)

# Dùng LẠI đúng hàm chuẩn hoá URL, hàm che khoá, và hằng User-Agent của lớp
# thật (llm/tuongthich_openai.py) — để script này kiểm ĐÚNG CÁI mà tiến
# trình chatbot thật sự sẽ gọi, không phải một bản chép tay có thể lệch.
from llm.tuongthich_openai import chuan_hoa_goc, che_khoa, _USER_AGENT  # noqa: E402


_THOI_GIAN_CHO = 30.0  # giây
_MAX_TOKENS_THU = 16  # lượt thử rất ngắn — không cần câu trả lời dài
_CAU_HOI_THU = 'Chào bạn, 1 cộng 1 bằng mấy? Trả lời một câu ngắn.'


def _doc_cau_hinh_bat_buoc():
    """Đọc biến môi trường bắt buộc; thiếu thì báo rõ tên biến rồi thoát
    mã 2 — KHÔNG gọi mạng khi cấu hình chưa đủ."""
    goc = os.environ.get('LLM_GOC', '').strip()
    khoa = os.environ.get('LLM_KHOA', '').strip()
    model_chinh = os.environ.get('LLM_MODEL_DIEN_GIAI', '').strip()

    thieu = [
        ten for ten, gia_tri in (
            ('LLM_GOC', goc), ('LLM_KHOA', khoa), ('LLM_MODEL_DIEN_GIAI', model_chinh),
        ) if not gia_tri
    ]
    if thieu:
        print(f"LỖI: thiếu biến môi trường bắt buộc: {', '.join(thieu)}.")
        print(
            'Đặt đủ LLM_GOC, LLM_KHOA, LLM_MODEL_DIEN_GIAI (và tuỳ chọn '
            'LLM_MODEL_DU_PHONG) trong môi trường rồi chạy lại script này.'
        )
        sys.exit(2)

    model_du_phong = os.environ.get('LLM_MODEL_DU_PHONG', '').strip() or None
    return goc, khoa, model_chinh, model_du_phong


def _thu_mot_model(url: str, khoa: str, model: str) -> dict:
    """Gọi cổng THẬT đúng một lượt rất ngắn bằng `model`. Trả về dict kết
    quả để in báo cáo — không ném lỗi ra ngoài, mọi thất bại (timeout,
    mạng, HTTP khác 200, định dạng sai, nội dung rỗng) đều gói vào
    ket_qua['ghi_chu'] để hàm main() in đủ thông tin rồi tự quyết ĐẠT/KHÔNG."""
    import httpx  # import trễ, giống mọi nhà cung cấp khác trong llm/

    than_bai = {
        'model': model,
        'max_tokens': _MAX_TOKENS_THU,
        'messages': [
            {'role': 'system', 'content': 'Trả lời cực ngắn, đúng một câu.'},
            {'role': 'user', 'content': _CAU_HOI_THU},
        ],
    }
    tieu_de = {
        'Authorization': f'Bearer {khoa}',
        'Content-Type': 'application/json',
        'User-Agent': _USER_AGENT,
    }

    ket_qua = {
        'model_gui': model, 'ma_http': None, 'model_tra_ve': None,
        'token_vao': None, 'token_ra': None, 'thoi_gian_ms': None,
        'dat': False, 'ghi_chu': '',
    }

    bat_dau = time.perf_counter()
    try:
        phan_hoi = httpx.post(url, json=than_bai, headers=tieu_de, timeout=_THOI_GIAN_CHO)
    except httpx.TimeoutException as loi:
        ket_qua['ghi_chu'] = f'TIMEOUT sau {_THOI_GIAN_CHO}s: {loi}'
        return ket_qua
    except httpx.HTTPError as loi:
        ket_qua['ghi_chu'] = f'LỖI MẠNG: {loi}'
        return ket_qua

    ket_qua['thoi_gian_ms'] = (time.perf_counter() - bat_dau) * 1000.0
    ket_qua['ma_http'] = phan_hoi.status_code

    if phan_hoi.status_code == 403:
        # Đây CHÍNH XÁC là điểm mấu chốt của script này — xem docstring
        # đầu tệp: model có trong /v1/models không có nghĩa là gọi được.
        ket_qua['ghi_chu'] = phan_hoi.text[:500]
        return ket_qua
    if phan_hoi.status_code != 200:
        ket_qua['ghi_chu'] = f'HTTP {phan_hoi.status_code}: {phan_hoi.text[:300]}'
        return ket_qua

    try:
        du_lieu = phan_hoi.json()
        ket_qua['model_tra_ve'] = du_lieu.get('model')
        van_ban = du_lieu['choices'][0]['message']['content']
        su_dung = du_lieu.get('usage') or {}
        ket_qua['token_vao'] = su_dung.get('prompt_tokens', su_dung.get('input_tokens'))
        ket_qua['token_ra'] = su_dung.get('completion_tokens', su_dung.get('output_tokens'))
    except (ValueError, KeyError, IndexError, TypeError) as loi:
        ket_qua['ghi_chu'] = f'Phản hồi HTTP 200 nhưng SAI ĐỊNH DẠNG mong đợi: {loi}'
        return ket_qua

    if not van_ban or not van_ban.strip():
        ket_qua['ghi_chu'] = 'Phản hồi HTTP 200 nhưng NỘI DUNG RỖNG.'
        return ket_qua

    ket_qua['dat'] = True
    return ket_qua


def main() -> None:
    goc, khoa, model_chinh, model_du_phong = _doc_cau_hinh_bat_buoc()
    goc_chuan = chuan_hoa_goc(goc)
    url = f'{goc_chuan}/v1/chat/completions'

    print('=' * 78)
    print('KIỂM CỔNG AI BÁN LẠI — GỌI THẬT, TỐN TIỀN THẬT MỖI LẦN CHẠY')
    print('=' * 78)
    print(f'LLM_GOC (đã chuẩn hoá) = {goc_chuan}')
    print(f'URL gọi                = {url}')
    print(f'LLM_KHOA                = {che_khoa(khoa)}')
    print(f'User-Agent              = {_USER_AGENT}')
    print()

    cac_model = [('LLM_MODEL_DIEN_GIAI (model chính)', model_chinh)]
    if model_du_phong:
        cac_model.append(('LLM_MODEL_DU_PHONG (model dự phòng)', model_du_phong))
    else:
        print('(LLM_MODEL_DU_PHONG chưa đặt — chỉ kiểm model chính.)\n')

    ket_qua_theo_model = []
    for nhan, model in cac_model:
        print('-' * 78)
        print(f'-> Đang gọi {nhan}: {model!r} ...')
        kq = _thu_mot_model(url, khoa, model)
        ket_qua_theo_model.append((nhan, model, kq))

        print(f'   model gửi     : {kq["model_gui"]}')
        print(f'   HTTP code     : {kq["ma_http"]}')
        print(f'   model trả về  : {kq["model_tra_ve"]}')
        print(f'   token vào/ra  : {kq["token_vao"]}/{kq["token_ra"]}')
        if kq['thoi_gian_ms'] is not None:
            print(f'   thời gian ms  : {kq["thoi_gian_ms"]:.0f}')
        else:
            print('   thời gian ms  : (không đo được — lỗi trước khi có phản hồi)')

        if kq['ma_http'] == 403:
            print(f'   *** HTTP 403 — NGUYÊN VĂN cổng trả về: {kq["ghi_chu"]}')
        elif kq['ghi_chu']:
            print(f'   ghi chú       : {kq["ghi_chu"]}')

        if kq['model_tra_ve'] and kq['model_tra_ve'] != model:
            print(f'   !!! NGHI BỊ TRÁO MODEL: đã gửi {model!r} nhưng nhận model={kq["model_tra_ve"]!r}')
        print()

    print('=' * 78)
    print('KẾT LUẬN TỪNG MODEL')
    print('=' * 78)
    model_chinh_dat = True
    for nhan, model, kq in ket_qua_theo_model:
        trang_thai = 'ĐẠT' if kq['dat'] else 'KHÔNG ĐẠT'
        dong = f'  [{trang_thai}] {nhan} ({model!r})'
        if not kq['dat']:
            dong += f' — {kq["ghi_chu"] or ("HTTP " + str(kq["ma_http"]))}'
        print(dong)
        if nhan.startswith('LLM_MODEL_DIEN_GIAI') and not kq['dat']:
            model_chinh_dat = False

    print()
    if not model_chinh_dat:
        print(
            'THOÁT MÃ KHÁC 0: model chính (LLM_MODEL_DIEN_GIAI) KHÔNG dùng được với khoá này — '
            'ĐỪNG bật CHATBOT_NHA_CUNG_CAP=openai với cấu hình hiện tại.'
        )
        sys.exit(1)

    print('OK: model chính dùng được. Xem kỹ dòng model dự phòng ở trên (nếu có cấu hình) trước khi triển khai.')
    sys.exit(0)


if __name__ == '__main__':
    main()
