# -*- coding: utf-8 -*-
"""CHAY_THAT.PY — bản tin chạy thật mỗi sáng (03/10/2026).

Lịch (chủ dự án 02–03/10):
  06:25  api1 đẩy gói số liệu (ban_tin_api1/day_goi.sh -> /api/ban_tin_goi.php -> _du_lieu/goi.json)
  06:30  `soan`: dựng + viết bài sáng (bản 1 lời thường, bản 2 kèm chỉ số) và bài kết quả bot, lưu bảng ban_tin_lich,
         gửi THẺ cho chủ shop (Telegram riêng) + BÁO CÁO TỰ CHẤM riêng (bao_cao_rieng).
  07:00  `dang`: bài kết quả bot tự đăng (T3–T7 ngày hôm trước, CN cả tuần; T2 không có).
  08:00  `dang`: bài sáng tự đăng BẢN 2 nếu chủ chưa bấm.
  Nút trên thẻ (tele-duyet.php -> cổng /bantin/hanh-dong): tn2 đăng bản 2 ngay, tn1 đăng bản 1 ngay, tnb bỏ.

Công tắc chat_cau_hinh (khong_gian tradingauto) `bt_che_do`:
  'tat'  — không làm gì;  'thu' — chỉ thẻ + báo cáo riêng + bản nháp web (chỉ admin xem), KHÔNG đăng kênh nào;
  'that' — đăng web công khai + Telegram chủ đề "Kênh tin tức" + Zalo nhóm đã chọn (Admin -> Zalo cá nhân) + Fanpage.
Fanpage tự đăng là quyết định của chủ dự án cho riêng bản tin (02/10: "8:00 chưa bấm thì tự đăng", bài kết quả "tự đăng").

Dùng:  python -m ban_tin.chay_that soan [yyyy-mm-dd]   |   python -m ban_tin.chay_that dang
"""
from __future__ import annotations

import json
import logging
import os
import sys
from datetime import date, datetime, timedelta
from typing import Any, Callable, Dict, List, Optional, Tuple

from congcu import csdl

_log = logging.getLogger('chatbot.ban_tin.chay_that')
KG = 'tradingauto'
DU_LIEU = os.path.join(os.path.dirname(os.path.abspath(__file__)), '_du_lieu')
TEP_GOI = os.path.join(DU_LIEU, 'goi.json')
TEN_LOAI = {'A': 'Bản tin sáng', 'M': 'Tầm nhìn tháng', 'S': 'Kết phiên thứ Sáu', 'T': 'Tổng kết + tầm nhìn tuần',
            'B': 'Kết quả bot', 'D': 'Kết quả bot cả tuần'}
GOC_WEB = 'https://tradingauto.org'


# ---------------------------------------------------------------------
# CẤU HÌNH + TELEGRAM RIÊNG
# ---------------------------------------------------------------------

def che_do(_lay_ket_noi=None) -> str:
    def _doc(ct):
        ct.execute("SELECT gia_tri FROM chat_cau_hinh WHERE khong_gian = %s AND khoa = 'bt_che_do'", (KG,))
        return str((ct.fetchone() or {}).get('gia_tri') or 'tat')
    v = csdl.voi_ct(_lay_ket_noi, _doc)
    return v if v in ('tat', 'thu', 'that') else 'tat'


def _tele_rieng(phuong_thuc: str, du: Dict[str, Any], tep_anh: Optional[str] = None) -> Optional[Dict[str, Any]]:
    from kenh.phieu import _cau_hinh_tele, _tele
    token, chat = csdl.voi_ct(None, _cau_hinh_tele)
    if not token or not chat:
        return None
    du = dict(du, chat_id=chat)
    if tep_anh:
        import httpx
        with open(tep_anh, 'rb') as f:
            return httpx.post(f'https://api.telegram.org/bot{token}/{phuong_thuc}', data=du,
                              files={'photo': (os.path.basename(tep_anh), f, 'image/png')}, timeout=60).json()
    return _tele(token, phuong_thuc, du)


def _bao_loi(van_ban: str) -> None:
    _log.warning(van_ban)
    try:
        _tele_rieng('sendMessage', {'text': '⚠️ Bản tin: ' + van_ban[:3500]})
    except Exception:  # noqa: BLE001
        pass


# ---------------------------------------------------------------------
# SOẠN (06:30)
# ---------------------------------------------------------------------

def _doc_goi(ngay: date) -> Optional[dict]:
    if not os.path.isfile(TEP_GOI):
        return None
    if datetime.now().timestamp() - os.path.getmtime(TEP_GOI) > 6 * 3600:
        return None                        # gói cũ quá 6 giờ = api1 không đẩy sáng nay
    return json.load(open(TEP_GOI, encoding='utf-8'))


def _lay_dong(ngay: date, kenh_bai: str, _lay_ket_noi=None) -> Optional[Dict[str, Any]]:
    def _doc(ct):
        ct.execute('SELECT * FROM ban_tin_lich WHERE ngay = %s AND kenh_bai = %s', (ngay.isoformat(), kenh_bai))
        return ct.fetchone()
    return csdl.voi_ct(_lay_ket_noi, _doc)


def _luu_dong(ngay: date, kenh_bai: str, loai: str, ban1: dict, ban2: Optional[dict], anh1: str, anh2: str,
              ban_mac_dinh: int, han: datetime, trang_thai: str) -> int:
    def _viec(ct):
        ct.execute("""INSERT INTO ban_tin_lich (ngay, kenh_bai, loai, ban1, ban2, anh1, anh2, ban_mac_dinh, han_dang, trang_thai,
                        tao_luc, cap_nhat) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,NOW(),NOW())
                      ON DUPLICATE KEY UPDATE loai=VALUES(loai), ban1=VALUES(ban1), ban2=VALUES(ban2), anh1=VALUES(anh1),
                        anh2=VALUES(anh2), ban_mac_dinh=VALUES(ban_mac_dinh), han_dang=VALUES(han_dang),
                        trang_thai=VALUES(trang_thai), tele_msg_id=NULL, ket_qua=NULL, cap_nhat=NOW()""",
                   (ngay.isoformat(), kenh_bai, loai, json.dumps(ban1, ensure_ascii=False),
                    json.dumps(ban2, ensure_ascii=False) if ban2 else None, anh1, anh2, ban_mac_dinh,
                    han.strftime('%Y-%m-%d %H:%M:%S'), trang_thai))
        ct.execute('SELECT id FROM ban_tin_lich WHERE ngay = %s AND kenh_bai = %s', (ngay.isoformat(), kenh_bai))
        return int(ct.fetchone()['id'])
    return csdl.voi_ct(None, _viec)


def _sach(b: Dict[str, Any]) -> Dict[str, Any]:
    return {k: b[k] for k in ('fb', 'tele', 'zalo', 'web', 'loi') if k in b}


def soan(ngay: Optional[date] = None, ep: bool = False) -> List[str]:
    """Dựng + viết + lưu + gửi thẻ. Chạy lại được: dòng đã có (không lỗi) thì bỏ qua, trừ khi ep=True."""
    from ban_tin import bao_cao_rieng, su_kien
    from ban_tin.bieu_do import chup
    from ban_tin.dien_tap import bai_ket_qua, bai_sang, bao_cao, loai_bai_sang
    from ban_tin.ket_qua_bot import so_lieu_bot
    from ban_tin.viet import viet

    ngay = ngay or date.today()
    cd_hien = che_do()
    if cd_hien == 'tat':
        return ['Bản tin đang TẮT (bt_che_do).']
    goi = _doc_goi(ngay)
    if goi is None:
        _bao_loi(f'sáng {ngay:%d/%m} chưa có gói số liệu mới từ api1 — không soạn được bản tin.')
        return ['thiếu gói số liệu']
    su_kien.cap_nhat()
    os.makedirs(os.path.join(DU_LIEU, 'anh'), exist_ok=True)
    bao, dem = [], {}
    kqb = bai_ket_qua(goi, ngay)
    he_thong = {'viptrend_mo': (kqb[4] if kqb else so_lieu_bot(ngay))['viptrend_mo']}

    cu = _lay_dong(ngay, 'sang')
    if ep or not cu or cu['trang_thai'] == 'loi':
        bai, anh = {}, {}
        for chi_so in (False, True):
            k = 2 if chi_so else 1
            cd, dk, loai, trang = bai_sang(goi, ngay, chi_so, he_thong, dem)
            bai[k] = _sach(viet(cd, dk, loai))
            anh[k] = os.path.join(DU_LIEU, 'anh', f'{ngay.isoformat()}_sang_{k}.png')
            open(anh[k], 'wb').write(chup(trang, 1200, 675))
        loi = bai[2].get('loi') or []
        id_ = _luu_dong(ngay, 'sang', loai, bai[1], bai[2], anh[1], anh[2], 2,
                        datetime.combine(ngay, datetime.min.time()) + timedelta(hours=8), 'loi' if loi else 'cho_duyet')
        _gui_the(id_, cd_hien)
        bao.append(f'bài sáng #{id_} ({TEN_LOAI[loai]})' + (f' LỖI KIỂM: {loi}' if loi else ''))
    if kqb:
        cu = _lay_dong(ngay, 'ket_qua')
        if ep or not cu or cu['trang_thai'] == 'loi':
            cd, dk, loai, trang, _kq = kqb
            b = _sach(viet(cd, dk, loai))
            tep = os.path.join(DU_LIEU, 'anh', f'{ngay.isoformat()}_ket_qua.png')
            open(tep, 'wb').write(chup(trang, 1200))
            id_ = _luu_dong(ngay, 'ket_qua', loai, b, None, tep, '', 1,
                            datetime.combine(ngay, datetime.min.time()) + timedelta(hours=7),
                            'loi' if b.get('loi') else 'cho_duyet')
            _gui_the(id_, cd_hien)
            bao.append(f'bài kết quả #{id_} ({TEN_LOAI[loai]})' + (f" LỖI KIỂM: {b['loi']}" if b.get('loi') else ''))
    try:
        van_ban = bao_cao(goi, ngay, dem)
        if van_ban:
            bao_cao_rieng.gui(van_ban)
    except Exception:  # noqa: BLE001
        _log.exception('Không gửi được báo cáo tự chấm.')
    return bao


# ---------------------------------------------------------------------
# THẺ TELEGRAM CHO CHỦ SHOP
# ---------------------------------------------------------------------

def _the_van_ban(d: Dict[str, Any], cd_hien: str) -> str:
    ten = TEN_LOAI.get(d['loai'], d['loai'])
    han = d['han_dang'].strftime('%H:%M') if hasattr(d['han_dang'], 'strftime') else str(d['han_dang'])[11:16]
    dong = [f"🗞 {ten.upper()} {d['ngay'].strftime('%d/%m') if hasattr(d['ngay'], 'strftime') else d['ngay']} · #{d['id']}"]
    if cd_hien == 'thu':
        dong.append('Chế độ THỬ: bấm đăng chỉ tạo bản nháp web, không lên kênh nào.')
    tt = d['trang_thai']
    if tt == 'cho_duyet':
        dong.append(f"Tự đăng {'bản 2' if d['ban_mac_dinh'] == 2 and d.get('ban2') else 'bài'} lúc {han} nếu anh chưa bấm.")
    elif tt == 'loi':
        dong.append('⚠️ Bài không qua bộ kiểm — KHÔNG tự đăng. Anh xem rồi bấm đăng nếu vẫn dùng được.')
    elif tt == 'da_dang':
        dong.append(f"✅ Đã đăng bản {d.get('ban_da_dang')} — {d.get('nguoi_duyet') or ''}")
    elif tt == 'bo':
        dong.append(f"⚪ Đã bỏ — {d.get('nguoi_duyet') or ''}")
    elif tt == 'dang_gui':
        dong.append('⏳ Đang đăng…')
    for k in ((2, 1) if d.get('ban2') else (1,)):
        b = json.loads(d[f'ban{k}'] or '{}')
        dong += ['━━━━━━━━━━', (f'Bản {k} — ' if d.get('ban2') else '') + 'khổ Telegram:', (b.get('tele') or '')[:1400]]
        if b.get('loi'):
            dong.append('Lỗi kiểm: ' + '; '.join(b['loi']))
    if d.get('ket_qua'):
        dong += ['━━━━━━━━━━', 'Kết quả đăng:', str(d['ket_qua'])[:800]]
    return '\n'.join(dong)[:4000]


def _nut(d: Dict[str, Any]) -> List[List[Dict[str, str]]]:
    from kenh.phieu import _bi_mat_ky, ky
    if d['trang_thai'] not in ('cho_duyet', 'loi'):
        return []
    bi_mat = csdl.voi_ct(None, _bi_mat_ky)
    n = lambda chu, h: {'text': chu, 'callback_data': f"{h}:{int(d['id'])}:{ky(bi_mat, h, int(d['id']))}"}  # noqa: E731
    if d.get('ban2'):
        return [[n('✅ Đăng bản 2', 'tn2'), n('Đăng bản 1', 'tn1')], [n('❌ Bỏ', 'tnb')]]
    return [[n('✅ Đăng ngay', 'tn1'), n('❌ Bỏ', 'tnb')]]


def _doc_id(id_: int) -> Optional[Dict[str, Any]]:
    def _doc(ct):
        ct.execute('SELECT * FROM ban_tin_lich WHERE id = %s', (int(id_),))
        return ct.fetchone()
    return csdl.voi_ct(None, _doc)


def _gui_the(id_: int, cd_hien: str) -> None:
    d = _doc_id(id_)
    anh = d['anh2'] or d['anh1']
    if anh and os.path.isfile(anh):
        _tele_rieng('sendPhoto', {'caption': f"Ảnh kèm #{id_}"}, anh)
    j = _tele_rieng('sendMessage', {'text': _the_van_ban(d, cd_hien), 'disable_web_page_preview': True,
                                    'reply_markup': json.dumps({'inline_keyboard': _nut(d)})}) or {}
    mid = (j.get('result') or {}).get('message_id') if isinstance(j, dict) else None
    if mid:
        csdl.voi_ct(None, lambda ct: ct.execute('UPDATE ban_tin_lich SET tele_msg_id = %s WHERE id = %s', (int(mid), id_)))


def _ve_lai_the(id_: int) -> None:
    d = _doc_id(id_)
    if not d or not d.get('tele_msg_id'):
        return
    _tele_rieng('editMessageText', {'message_id': int(d['tele_msg_id']), 'text': _the_van_ban(d, che_do()),
                                    'disable_web_page_preview': True,
                                    'reply_markup': json.dumps({'inline_keyboard': _nut(d)})})


# ---------------------------------------------------------------------
# ĐĂNG
# ---------------------------------------------------------------------

def _giu(id_: int, tu: Tuple[str, ...] = ('cho_duyet',)) -> bool:
    """Giữ chỗ trước khi đăng: chỉ MỘT tiến trình đổi được trạng thái sang dang_gui (chống đăng hai lần)."""
    def _viec(ct):
        ct.execute(f"UPDATE ban_tin_lich SET trang_thai = 'dang_gui', cap_nhat = NOW() WHERE id = %s AND trang_thai IN "
                   f"({','.join(['%s'] * len(tu))})", (int(id_), *tu))
        return ct.rowcount == 1
    return csdl.voi_ct(None, _viec)


def _ket_thuc(id_: int, trang_thai: str, ban: Optional[int], nguoi: str, ket_qua: str) -> None:
    csdl.voi_ct(None, lambda ct: ct.execute(
        'UPDATE ban_tin_lich SET trang_thai = %s, ban_da_dang = %s, nguoi_duyet = %s, ket_qua = %s, cap_nhat = NOW() '
        'WHERE id = %s', (trang_thai, ban, nguoi[:120], ket_qua[:4000], int(id_))))


def phat(d: Dict[str, Any], ban: int, cd_hien: str) -> List[str]:
    """Đăng bài (bản `ban`) lên các kênh. Trả danh sách kết quả từng kênh."""
    from ban_tin.phat import THU_MUC_ANH, luu_web, phat_tele, slug, tele_dich
    b = json.loads(d[f'ban{ban}'] or '{}')
    anh = d[f'anh{ban}'] or d['anh1']
    ngay = d['ngay'] if isinstance(d['ngay'], date) else date.fromisoformat(str(d['ngay']))
    loai_web = d['loai']
    ra = []
    if cd_hien == 'thu':
        duong = luu_web(ngay, loai_web, ban, b['web'], anh, trang_thai='nhap', thu=True)
        return [f'web nháp: {GOC_WEB}{duong} (chế độ thử, không đăng kênh)']
    duong = luu_web(ngay, loai_web, 1, b['web'], anh, trang_thai='cong_khai')
    link = GOC_WEB + duong
    anh_url = f"{GOC_WEB}/{THU_MUC_ANH}/{slug(ngay, loai_web, 1)}.png"
    ra.append(f'web: {link}')
    tele_chu = (b.get('tele') or '').replace(GOC_WEB + '/tin-tuc', link)
    try:
        token, chat, chu_de = csdl.voi_ct(None, tele_dich)
        ok, loi = phat_tele(token, chat, chu_de, tele_chu, anh) if chat else (False, 'chưa có chủ đề Kênh tin tức')
        ra.append('Telegram: ' + ('đã đăng' if ok else 'LỖI ' + loi))
    except Exception as e:  # noqa: BLE001
        ra.append(f'Telegram: LỖI {e}')
    try:
        from kenh import zalo_cn
        kq = zalo_cn.phat_ban_tin((b.get('zalo') or '').replace(GOC_WEB + '/tin-tuc', link), anh)
        ra.append('Zalo: ' + (', '.join(f"{t} {'ok' if o else 'LỖI ' + str(l)[:60]}" for t, o, l in kq) or 'tắt / chưa chọn nhóm'))
    except Exception as e:  # noqa: BLE001
        ra.append(f'Zalo: LỖI {e}')
    try:
        from kenh.bai_dang import _dang_fb, _lay_trang
        trang = csdl.voi_ct(None, lambda ct: _lay_trang(ct, KG))
        if trang:
            ok, _pid, fb_link, loi = _dang_fb(trang, {'noi_dung': b.get('fb') or '', 'anh_url': anh_url})
            ra.append('Fanpage: ' + (fb_link or 'đã đăng' if ok else 'LỖI ' + loi))
        else:
            ra.append('Fanpage: chưa kết nối Trang')
    except Exception as e:  # noqa: BLE001
        ra.append(f'Fanpage: LỖI {e}')
    return ra


def _dang_mot(id_: int, ban: int, nguoi: str, tu: Tuple[str, ...]) -> Tuple[bool, str]:
    if not _giu(id_, tu):
        return False, f'Bài #{id_} đã được xử lý trước đó.'
    d = _doc_id(id_)
    if ban == 2 and not d.get('ban2'):
        ban = 1
    try:
        ra = phat(d, ban, che_do())
        _ket_thuc(id_, 'da_dang', ban, nguoi, '\n'.join(ra))
        _ve_lai_the(id_)
        return True, f'Đã đăng bản {ban}.'
    except Exception as e:  # noqa: BLE001
        _log.exception('Đăng bản tin #%s lỗi', id_)
        _ket_thuc(id_, 'loi', None, nguoi, f'Lỗi khi đăng: {e}')
        _ve_lai_the(id_)
        return False, f'Lỗi khi đăng: {e}'


def dang(bay_gio: Optional[datetime] = None) -> List[str]:
    """Đăng các bài tới hạn mà chủ chưa bấm (bài lỗi kiểm KHÔNG tự đăng)."""
    bay_gio = bay_gio or datetime.now()
    if che_do() == 'tat':
        return []

    def _doc(ct):
        ct.execute("SELECT id, ban_mac_dinh FROM ban_tin_lich WHERE trang_thai = 'cho_duyet' AND han_dang <= %s "
                   "AND han_dang >= %s", (bay_gio.strftime('%Y-%m-%d %H:%M:%S'),
                                          (bay_gio - timedelta(hours=3)).strftime('%Y-%m-%d %H:%M:%S')))
        return list(ct.fetchall() or [])
    return [f"#{r['id']}: " + _dang_mot(int(r['id']), int(r['ban_mac_dinh']), 'tự đăng theo giờ', ('cho_duyet',))[1]
            for r in csdl.voi_ct(None, _doc)]


def hanh_dong(id_: int, hanh: str, nguoi: str) -> Tuple[bool, str]:
    """Nút trên thẻ: tn2 / tn1 đăng ngay bản 2 / bản 1, tnb bỏ."""
    if hanh == 'tnb':
        def _viec(ct):
            ct.execute("UPDATE ban_tin_lich SET trang_thai = 'bo', nguoi_duyet = %s, cap_nhat = NOW() "
                       "WHERE id = %s AND trang_thai IN ('cho_duyet','loi')", (nguoi[:120], int(id_)))
            return ct.rowcount == 1
        ok = csdl.voi_ct(None, _viec)
        _ve_lai_the(id_)
        return ok, 'Đã bỏ bài.' if ok else 'Bài đã được xử lý trước đó.'
    if hanh in ('tn1', 'tn2'):
        return _dang_mot(id_, 2 if hanh == 'tn2' else 1, nguoi, ('cho_duyet', 'loi'))
    return False, 'Nút không hợp lệ.'


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    logging.getLogger('httpx').setLevel(logging.WARNING)      # httpx ghi cả URL có token bot Telegram vào nhật ký
    lenh = sys.argv[1] if len(sys.argv) > 1 else ''
    if lenh == 'soan':
        ngay_ = date.fromisoformat(sys.argv[2]) if len(sys.argv) > 2 and sys.argv[2] != '--ep' else None
        print('\n'.join(soan(ngay_, ep='--ep' in sys.argv)))
    elif lenh == 'dang':
        print('\n'.join(dang()))
    elif lenh == 'che-do' and len(sys.argv) > 2 and sys.argv[2] in ('tat', 'thu', 'that'):
        csdl.voi_ct(None, lambda ct: ct.execute(
            "INSERT INTO chat_cau_hinh (khong_gian, khoa, gia_tri, cap_nhat) VALUES (%s, 'bt_che_do', %s, NOW()) "
            "ON DUPLICATE KEY UPDATE gia_tri = VALUES(gia_tri), cap_nhat = NOW()", (KG, sys.argv[2])))
        print('bt_che_do =', sys.argv[2])
    else:
        print(__doc__)
