# -*- coding: utf-8 -*-
"""THU_SONG_SONG.PY — chứng minh số liệu token KHÔNG lẫn giữa hai khách.

VÌ SAO TỆP NÀY TỒN TẠI
----------------------
`NhaCungCapTuongThichOpenAI` là SINGLETON dùng chung cho cả tiến trình. Sau
mỗi lượt gọi nó ghi lại model / token vào / token ra của lượt đó, và
`nao/tra_loi.py` đọc mấy số ấy để gắn vào `TraLoi`, rồi `congcu/nhat_ky.py`
ghi xuống bảng `chat_hoi_thoai` — là bảng mà trang quản trị dựa vào để tính
chi phí và tỉ lệ phủ tầng 1.

Nếu mấy số đó là thuộc tính thường của đối tượng thì hai khách hỏi cùng lúc
sẽ ghi đè lên nhau: luồng A gọi xong, luồng B gọi và ĐÈ, rồi A mới đọc — A
lấy phải token của B.

Và đây là kiểu hỏng tệ nhất: KHÁCH VẪN NHẬN ĐÚNG CÂU TRẢ LỜI. Chỉ có bảng
chi phí là sai, sai âm thầm, và không có gì để đối chiếu ra.

Bộ này ép đúng tình huống đó: hai luồng gọi xen kẽ nhau bằng một hàng rào
đồng bộ, rồi mỗi luồng đọc lại số của chính nó. Chạy bộ này trên bản dùng
thuộc tính thường là TRƯỢT.

Chạy:  python thu/thu_song_song.py
"""
from __future__ import annotations

import logging
import os
import sys
import threading

_THU_MUC_CHA = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _THU_MUC_CHA not in sys.path:
    sys.path.insert(0, _THU_MUC_CHA)

SO_LUONG = 6


def chay() -> int:
    """Trả về số ca SAI. 0 nghĩa là đạt."""
    os.environ.update(LLM_GOC='https://vidu.test', LLM_KHOA='sk-KHOA-GIA-0000000000',
                      LLM_MODEL_DIEN_GIAI='model-thu')
    import httpx
    from llm.tuongthich_openai import NhaCungCapTuongThichOpenAI

    # Hàng rào: mọi luồng phải GỌI XONG hết rồi mới luồng nào được ĐỌC. Đây
    # chính là thứ tự xấu nhất, và là thứ tự sẽ xảy ra thật khi cổng bận.
    rao = threading.Barrier(SO_LUONG)
    ket_qua = {}
    khoa_in = threading.Lock()

    def post(url, json=None, headers=None, timeout=None, **kw):
        # Mỗi luồng có một con số riêng, suy từ tên luồng -> biết ngay lẫn hay không.
        n = int(threading.current_thread().name.split('-')[-1])
        return httpx.Response(
            200,
            json={'model': 'model-%d' % n,
                  'choices': [{'message': {'content': 'tra loi cua luong %d' % n},
                               'finish_reason': 'stop'}],
                  'usage': {'prompt_tokens': 1000 + n, 'completion_tokens': 100 + n}},
            request=httpx.Request('POST', 'https://vidu.test'))

    ncc = NhaCungCapTuongThichOpenAI()

    def mot_luong(n):
        ncc._goi('model-thu', 'he thong', 'nguoi dung %d' % n, 500)
        rao.wait()                      # ép mọi luồng gọi xong TRƯỚC khi ai đọc
        doc = (ncc.model_thuc_te_gan_nhat, ncc.token_vao_gan_nhat, ncc.token_ra_gan_nhat)
        with khoa_in:
            ket_qua[n] = doc

    that = httpx.post
    httpx.post = post
    try:
        cac_luong = [threading.Thread(target=mot_luong, args=(n,), name='khach-%d' % n)
                     for n in range(SO_LUONG)]
        for t in cac_luong:
            t.start()
        for t in cac_luong:
            t.join(timeout=20)
    finally:
        httpx.post = that

    print('=' * 84)
    print('SỐ LIỆU TOKEN CÓ LẪN GIỮA %d KHÁCH HỎI CÙNG LÚC KHÔNG' % SO_LUONG)
    print('=' * 84)
    print('%-8s %-14s %-14s %-14s %s' % ('luồng', 'model', 'token_vào', 'token_ra', ''))
    so_sai = 0
    for n in range(SO_LUONG):
        mong = ('model-%d' % n, 1000 + n, 100 + n)
        thuc = ket_qua.get(n)
        dung = (thuc == mong)
        so_sai += 0 if dung else 1
        print('%-8d %-14s %-14s %-14s %s'
              % (n, (thuc or ('?',))[0], (thuc or (0, '?'))[1], (thuc or (0, 0, '?'))[2],
                 'đạt' if dung else '<== LẪN, mong %s' % (mong,)))
    print('-' * 84)
    print('SỐ CA SAI: %d' % so_sai)
    if so_sai:
        print('Nghĩa là bảng chi phí trong trang quản trị sẽ ghi sai token cho từng câu.')
    return so_sai


if __name__ == '__main__':
    logging.disable(logging.CRITICAL)
    sys.exit(1 if chay() else 0)
