# -*- coding: utf-8 -*-
"""NHÀ CUNG CẤP ANTHROPIC — gọi HTTP thẳng, không dùng SDK.

VÌ SAO gọi thẳng bằng httpx thay vì SDK `anthropic`: bớt một dependency
ngoài chỉ để gói lại một request JSON đơn giản — httpx đã có sẵn trong dự
án cho những việc khác, và endpoint `/v1/messages` ổn định, không cần các
tiện ích nâng cao (streaming SDK, retry tự động...) mà SDK mang lại.

VÌ SAO import httpx TRỄ (trong __init__/hàm gọi, không ở đầu tệp): tệp này
có thể bị import chỉ để lấy tên lớp (vd bảng chọn nhà cung cấp) mà không
bao giờ thực sự gọi mạng — import trễ tránh bắt máy chạy test phải cài
httpx nếu nhánh đó không dùng tới, và tránh crash-khi-import nếu httpx
thiếu trên một môi trường tối giản.
"""
from __future__ import annotations

import os
import sys
from typing import List, Optional, Tuple

try:
    from .giao_dien import NhaCungCap, LoiNhaCungCap, dung_prompt_dien_giai, dung_prompt_phan_loai, CAC_NHAN_YDINH
    from ..giao_uoc import NguCanh, KetQuaTim, Ydinh
except ImportError:
    sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    from llm.giao_dien import (  # type: ignore
        NhaCungCap, LoiNhaCungCap, dung_prompt_dien_giai, dung_prompt_phan_loai, CAC_NHAN_YDINH,
    )
    from giao_uoc import NguCanh, KetQuaTim, Ydinh  # type: ignore


_GOC_API = 'https://api.anthropic.com/v1/messages'
_PHIEN_BAN_API = '2023-06-01'
_THOI_GIAN_CHO = 30.0  # giây

# Hai mô hình cấu hình được qua biến môi trường — không viết cứng để đổi
# đời mô hình (vd Haiku 4.5 -> 5) không cần sửa code, chỉ cần sửa .env.
_MODEL_PHAN_LOAI_MAC_DINH = 'claude-haiku-4-5-20251001'
_MODEL_DIEN_GIAI_MAC_DINH = 'claude-sonnet-5'

# 02/10/2026: bo tran 4000 cho TOM_TAT (chu du an) - tran chi con de chan chay mat kiem soat; xem tuongthich_openai.py.
_MAX_TOKENS_DIEN_GIAI = 32000
_MAX_TOKENS_PHAN_LOAI = 16
_MAX_TOKENS_TOM_TAT = 32000
_THOI_GIAN_CHO_NEN = 180.0


class NhaCungCapAnthropic(NhaCungCap):
    """Gọi thẳng Anthropic Messages API bằng httpx (không SDK)."""

    ten = 'anthropic'

    def __init__(self) -> None:
        khoa = os.environ.get('ANTHROPIC_API_KEY', '').strip()
        if not khoa:
            # Ném ngay lúc khởi tạo, không đợi tới lúc gọi — lỗi cấu hình
            # phải lộ ra sớm nhất có thể, không phải giữa lúc khách đang chờ.
            raise LoiNhaCungCap(
                'Thiếu biến môi trường ANTHROPIC_API_KEY — chưa thể khởi tạo '
                'NhaCungCapAnthropic. Kiểm tra tệp .env hoặc cấu hình môi trường '
                'của tiến trình chatbot.'
            )
        self._khoa = khoa
        self._model_phan_loai = os.environ.get('ANTHROPIC_MODEL_PHAN_LOAI', _MODEL_PHAN_LOAI_MAC_DINH)
        self._model_dien_giai = os.environ.get('ANTHROPIC_MODEL_DIEN_GIAI', _MODEL_DIEN_GIAI_MAC_DINH)

    def _goi(self, model: str, cau_dan_he_thong: str, cau_nguoi_dung: str, max_tokens: int) -> str:
        import httpx  # import trễ — xem lý do ở đầu tệp

        # Đệm prompt (prompt caching) cho câu dẫn hệ thống: phần này ĐỨNG
        # YÊN giữa các lượt gọi (chỉ đổi khi ta sửa code, không đổi theo
        # từng khách) trong khi phần người dùng đổi mỗi lượt. Đánh dấu
        # cache_control ở khối hệ thống để Anthropic tái dùng phần đã xử
        # lý — đọc lại từ bộ nhớ đệm chỉ tốn khoảng 0,1 lần giá gốc của
        # token đầu vào, tiết kiệm đáng kể khi ngân sách ngày có hạn
        # (xem bao_ve/ngan_sach.py).
        than_bai = {
            'model': model,
            'max_tokens': max_tokens,
            'system': [
                {
                    'type': 'text',
                    'text': cau_dan_he_thong,
                    'cache_control': {'type': 'ephemeral'},
                }
            ],
            'messages': [
                {'role': 'user', 'content': cau_nguoi_dung},
            ],
        }
        tieu_de = {
            'x-api-key': self._khoa,
            'anthropic-version': _PHIEN_BAN_API,
            'content-type': 'application/json',
        }

        try:
            cho = _THOI_GIAN_CHO_NEN if max_tokens == _MAX_TOKENS_TOM_TAT else _THOI_GIAN_CHO
            phan_hoi = httpx.post(_GOC_API, json=than_bai, headers=tieu_de, timeout=cho)
            phan_hoi.raise_for_status()
        except httpx.TimeoutException as loi:
            raise LoiNhaCungCap(f'Anthropic không phản hồi sau {cho}s (hết thời gian chờ): {loi}') from loi
        except httpx.HTTPStatusError as loi:
            raise LoiNhaCungCap(
                f'Anthropic trả lỗi HTTP {loi.response.status_code}: {loi.response.text[:500]}'
            ) from loi
        except httpx.HTTPError as loi:
            raise LoiNhaCungCap(f'Lỗi mạng khi gọi Anthropic: {loi}') from loi

        try:
            du_lieu = phan_hoi.json()
            cac_khoi = du_lieu['content']
            van_ban = ''.join(k.get('text', '') for k in cac_khoi if k.get('type') == 'text')
        except (ValueError, KeyError, TypeError) as loi:
            raise LoiNhaCungCap(f'Phản hồi Anthropic không đúng định dạng mong đợi: {loi}') from loi

        if not van_ban.strip():
            raise LoiNhaCungCap('Anthropic trả về nội dung rỗng.')
        return van_ban.strip()

    def phan_loai(self, cau_hoi: str, ngu_canh: NguCanh) -> Ydinh:
        he_thong, nguoi_dung = dung_prompt_phan_loai(cau_hoi, ngu_canh)
        nhan = self._goi(self._model_phan_loai, he_thong, nguoi_dung, _MAX_TOKENS_PHAN_LOAI)
        nhan = nhan.strip().strip('.').lower()
        if nhan not in CAC_NHAN_YDINH:
            # Mô hình đôi khi trả thêm chữ thừa dù đã dặn — cố cứu bằng
            # cách dò nhãn hợp lệ nằm trong chuỗi trả về, còn không được
            # thì rơi về 'khac' thay vì ném lỗi (phân loại sai vẫn nên
            # tiếp tục trả lời chứ không nên chặn cả lượt hỏi của khách).
            for ung_vien in CAC_NHAN_YDINH:
                if ung_vien in nhan:
                    return ung_vien  # type: ignore[return-value]
            return 'khac'
        return nhan  # type: ignore[return-value]

    def dien_giai(self, cau_hoi: str, cac_doan: List[KetQuaTim], ngu_canh: NguCanh) -> str:
        he_thong, nguoi_dung = dung_prompt_dien_giai(cau_hoi, cac_doan, ngu_canh)
        return self._goi(self._model_dien_giai, he_thong, nguoi_dung, _MAX_TOKENS_DIEN_GIAI)

    def tom_tat(self, cau_dan_he_thong: str, cau_nguoi_dung: str, mo_hinh: Optional[str] = None) -> str:
        # Chạy nền, không ai chờ — dùng model phân loại (Haiku) cho rẻ.
        return self._goi(self._model_phan_loai, cau_dan_he_thong, cau_nguoi_dung, _MAX_TOKENS_TOM_TAT)


if __name__ == '__main__':
    # ---- Tự kiểm KHÔNG gọi mạng thật: chỉ chứng minh lỗi thiếu khoá ----
    khoa_cu = os.environ.pop('ANTHROPIC_API_KEY', None)
    try:
        print("=== Kiểm thiếu ANTHROPIC_API_KEY ===")
        try:
            NhaCungCapAnthropic()
            print("SAI: lẽ ra phải ném LoiNhaCungCap")
        except LoiNhaCungCap as loi:
            print("OK, ném đúng LoiNhaCungCap:")
            print(" ", loi)
    finally:
        if khoa_cu is not None:
            os.environ['ANTHROPIC_API_KEY'] = khoa_cu
