# -*- coding: utf-8 -*-
"""ANH.PY — tải và chuẩn bị ẢNH khách gửi để đi CÙNG lượt trả lời.

VÌ SAO (25/09/2026)
-------------------
Chủ dự án: "khách gửi ảnh và ai ko nhận và đọc hiểu được ảnh". Webhook trước
đây thay ảnh bằng câu "(Khách gửi 1 hình ảnh/tệp)" — bot mù đúng lúc khách cần
nhất (ảnh chụp lỗi MT5/EA, ảnh chart, ảnh chuyển khoản).

Chủ dự án tiếp: "nếu trong phiên ảnh đã hiểu được tốt thì không nên tách ra khỏi
phiên mất tính liên tục" — nên KHÔNG có lượt "đọc ảnh" riêng. Tệp này chỉ:
  1. tải ảnh từ link Messenger NGAY (link có hạn),
  2. đổi JPEG, thu nhỏ cạnh dài về CANH_DAI (đủ đọc chữ lỗi, bớt token),
  3. lưu `private/du_lieu/anh_chat/<tháng>/` (tên = băm nội dung), tự xoá sau GIU_NGAY ngày,
  4. trả base64 cho NguCanh.anh — nao/tra_loi.py gửi ảnh cùng lịch sử + tài liệu
     trong MỘT lượt gọi mô hình trả lời; mô hình tự khai loại ảnh + dòng mô tả,
     dòng mô tả vào lịch sử thay cho ảnh ở các lượt sau.

Ảnh gửi mô hình dạng base64: đo 25/09/2026, claude-opus-5 qua cổng hhtechapi
KHÔNG thấy ảnh gửi bằng link.

Chạy tự kiểm (không gọi mạng):  python kenh/anh.py
"""
from __future__ import annotations

import base64
import hashlib
import io
import logging
import os
import threading
import time
from typing import Callable, List, Optional, Tuple

_log = logging.getLogger('chatbot.kenh.anh')

_THU_MUC_CHATBOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
THU_MUC = os.environ.get('CHATBOT_THU_MUC_ANH') or os.path.normpath(
    os.path.join(_THU_MUC_CHATBOT, '..', '..', 'du_lieu', 'anh_chat'))
GIU_NGAY = 30
TOI_DA_BYTE = 8 * 1024 * 1024
CANH_DAI = 1600
TOI_DA_ANH = 4

_lan_don = [0.0]
_khoa_don = threading.Lock()


def tai_ve(url: str, _get: Optional[Callable[[str], Tuple[int, bytes, str]]] = None) -> Optional[bytes]:
    """Tải ảnh từ link Messenger. None nếu lỗi / quá lớn / không phải ảnh."""
    if not url.startswith('https://'):
        return None
    try:
        if _get is None:
            import httpx
            r = httpx.get(url, timeout=20, follow_redirects=True)
            ma, du_lieu, kieu = r.status_code, r.content, r.headers.get('content-type', '')
        else:
            ma, du_lieu, kieu = _get(url)
    except Exception as loi:  # noqa: BLE001
        _log.warning('Không tải được ảnh khách gửi: %s', loi)
        return None
    if ma != 200 or not du_lieu or len(du_lieu) > TOI_DA_BYTE or (kieu and not kieu.startswith('image/')):
        _log.warning('Ảnh khách gửi không dùng được: HTTP %s, %s byte, %s', ma, len(du_lieu or b''), kieu)
        return None
    return du_lieu


def chuan_hoa(du_lieu: bytes) -> Optional[bytes]:
    """JPEG, cạnh dài <= CANH_DAI. None nếu không mở được như ảnh."""
    try:
        from PIL import Image
        im = Image.open(io.BytesIO(du_lieu)).convert('RGB')
        if max(im.size) > CANH_DAI:
            tl = CANH_DAI / float(max(im.size))
            im = im.resize((max(1, int(im.width * tl)), max(1, int(im.height * tl))))
        ra = io.BytesIO()
        im.save(ra, 'JPEG', quality=85)
        return ra.getvalue()
    except Exception as loi:  # noqa: BLE001
        _log.warning('Không mở được ảnh: %s', loi)
        return None


def luu(jpeg: bytes, thu_muc: str = THU_MUC) -> str:
    """Lưu theo tháng, tên = băm nội dung (ảnh trùng không lưu hai lần). Trả đường dẫn."""
    thang = time.strftime('%Y%m')
    os.makedirs(os.path.join(thu_muc, thang), mode=0o700, exist_ok=True)
    duong = os.path.join(thu_muc, thang, hashlib.sha256(jpeg).hexdigest()[:24] + '.jpg')
    if not os.path.exists(duong):
        with open(duong, 'wb') as f:
            f.write(jpeg)
    return duong


def don_cu(thu_muc: str = THU_MUC, giu_ngay: int = GIU_NGAY) -> int:
    """Xoá ảnh cũ hơn giu_ngay ngày. Trả số tệp đã xoá."""
    han = time.time() - giu_ngay * 86400
    xoa = 0
    for goc, _, tep in os.walk(thu_muc):
        for t in tep:
            p = os.path.join(goc, t)
            try:
                if os.path.getmtime(p) < han:
                    os.remove(p)
                    xoa += 1
            except OSError:
                pass
    return xoa


def _don_dinh_ky(thu_muc: str) -> None:
    with _khoa_don:
        if time.time() - _lan_don[0] < 6 * 3600:
            return
        _lan_don[0] = time.time()
    try:
        n = don_cu(thu_muc)
        if n:
            _log.info('Đã xoá %d ảnh khách gửi quá %d ngày.', n, GIU_NGAY)
    except Exception:  # noqa: BLE001
        _log.exception('Dọn ảnh cũ lỗi.')


def doc_tep(duong: str) -> Optional[str]:
    """Base64 của ảnh đã lưu (lô bị xử lý lại sau khi link Messenger hết hạn)."""
    try:
        with open(duong, 'rb') as f:
            return base64.b64encode(f.read()).decode()
    except OSError:
        return None


def chuan_bi(cac_url: List[str], *, _tai: Optional[Callable[[str], Optional[bytes]]] = None,
             thu_muc: str = THU_MUC) -> Tuple[List[str], List[str]]:
    """Tải + chuẩn hoá + lưu. Trả (danh sách base64, danh sách đường dẫn đã lưu)."""
    _don_dinh_ky(thu_muc)
    b64: List[str] = []
    tep: List[str] = []
    for u in cac_url[:TOI_DA_ANH]:
        tho = (_tai or tai_ve)(u)
        jpeg = chuan_hoa(tho) if tho else None
        if not jpeg:
            continue
        tep.append(luu(jpeg, thu_muc))
        b64.append(base64.b64encode(jpeg).decode())
    return b64, tep


# =====================================================================
# TỰ KIỂM
# =====================================================================
if __name__ == '__main__':
    import tempfile
    from PIL import Image
    logging.disable(logging.WARNING)

    print('=== chuan_bi: tải, thu nhỏ, lưu một lần, dọn cũ ===')
    ra = io.BytesIO(); Image.new('RGB', (3200, 1600), 'white').save(ra, 'PNG'); png = ra.getvalue()
    with tempfile.TemporaryDirectory() as td:
        b64, tep = chuan_bi(['https://fb/a', 'http://khong-https', 'https://fb/a'],
                            _tai=lambda u: png if u.startswith('https://') else None, thu_muc=td)
        assert len(b64) == 2 and len(tep) == 2 and tep[0] == tep[1], 'ảnh trùng lưu một tệp'
        assert max(Image.open(tep[0]).size) == CANH_DAI
        assert doc_tep(tep[0]) == b64[0]
        assert chuan_bi(['https://fb/c'], _tai=lambda u: b'khong phai anh', thu_muc=td) == ([], [])
        os.utime(tep[0], (time.time() - 40 * 86400,) * 2)
        assert don_cu(td) == 1
    print('  OK')
    print('\nTỰ KIỂM kenh/anh.py QUA.')
