# -*- coding: utf-8 -*-
"""BIEU_DO.PY — ảnh bản tin: biểu đồ nến kiểu TradingView + thẻ kết quả bot (02/10/2026).

Chủ dự án chọn TradingView Lightweight Charts 5.2.1 (Apache-2.0, đặt ở public/assets/vendor/lightweight-charts — đã
đối sha512 với bản npm) + "cài chromium luôn". Vẽ bằng HTML rồi chụp bằng Chromium headless (Playwright). Giữ dòng ghi
nguồn TradingView (attributionLogo mặc định của thư viện).
Thời gian trên trục = giờ VN (đưa epoch của đồng hồ VN, thư viện hiển thị như UTC).
"""
from __future__ import annotations

import html
import json
import os
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from ban_tin.phan_tich import ema, rsi

_GOC = os.path.dirname(os.path.abspath(__file__))
_TEP_LWC = 'assets/vendor/lightweight-charts/lightweight-charts-5.2.1.standalone.production.js'
MAU = {'nen': '#131722', 'chu': '#d1d4dc', 'luoi': '#1e222d', 'vien': '#2a2e39', 'tang': '#26a69a', 'giam': '#ef5350',
       'vang': '#f0b90b', 'mo': '#787b86'}
MAU_TRANG_THAI = {'RALLY': '#26a69a', 'SIDEWAY': '#f0b90b', 'SELLOFF': '#ff9800', 'CRASH': '#ef5350'}
PHONG = "'Be Vietnam Pro','Noto Sans','DejaVu Sans',Arial,sans-serif"


def _js_lwc() -> str:
    for goc in ('public_html', 'public'):
        p = os.path.normpath(os.path.join(_GOC, '..', '..', '..', '..', goc, _TEP_LWC))
        if os.path.isfile(p):
            return open(p, encoding='utf-8').read()
    raise FileNotFoundError('Không thấy ' + _TEP_LWC)


def chup(trang: str, rong: int, cao: Optional[int] = None) -> bytes:
    """Chụp trang HTML (tự chứa) -> PNG. cao=None: cao theo nội dung #khung."""
    from playwright.sync_api import sync_playwright
    with sync_playwright() as p:
        tr = p.chromium.launch(args=['--no-sandbox', '--disable-gpu'])
        try:
            pg = tr.new_page(viewport={'width': rong, 'height': cao or 900}, device_scale_factor=1)
            pg.set_content(trang, wait_until='load')
            pg.wait_for_function('window.__xong === true', timeout=15000)
            el = pg.query_selector('#khung')
            return el.screenshot(type='png')
        finally:
            tr.close()


def _epoch_vn(chuoi: str) -> int:
    """'2026-10-02 06:00' (giờ VN) -> epoch mà thư viện hiển thị đúng 06:00."""
    return int(datetime.strptime(chuoi, '%Y-%m-%d %H:%M').replace(tzinfo=timezone.utc).timestamp())


def _vn(x: float, so: int = 2) -> str:
    s = f'{x:,.{so}f}'
    return s.replace(',', '§').replace('.', ',').replace('§', '.')


# =====================================================================
# BIỂU ĐỒ BẢN TIN (phần A)
# =====================================================================

def html_bieu_do(sl: Dict[str, Any], chi_so: bool, rong: int = 1200, cao: int = 675) -> str:
    h1 = sl['h1']
    nen = [{'time': _epoch_vn(b[0]), 'open': b[1], 'high': b[2], 'low': b[3], 'close': b[4]} for b in h1]
    dong = [b[4] for b in h1]
    du = {'nen': nen, 'vung': sl['vung'], 'chi_so': chi_so}
    if chi_so:
        du['ema20'] = [{'time': n['time'], 'value': v} for n, v in zip(nen, ema(dong, 20)) if v is not None]
        du['ema50'] = [{'time': n['time'], 'value': v} for n, v in zip(nen, ema(dong, 50)) if v is not None]
        du['rsi'] = [{'time': n['time'], 'value': v} for n, v in zip(nen, rsi(dong, 14)) if v is not None]
    tt = sl.get('trang_thai') or {}
    mau_tt = MAU_TRANG_THAI.get(tt.get('ma'), MAU['mo'])
    ngay = datetime.strptime(sl['ngay_ban_tin'], '%Y-%m-%d').strftime('%d/%m/%Y')
    hq = sl['hom_qua']
    mau_doi = MAU['tang'] if hq['thay_doi'] >= 0 else MAU['giam']
    nhan_tt = (f"{tt.get('ten', '')}" + (f" · vừa chuyển từ {tt.get('tu')}" if tt.get('vua_chuyen') else '')) if tt else ''
    return f"""<!doctype html><html><head><meta charset="utf-8"><style>
*{{box-sizing:border-box;margin:0;padding:0}}
body{{background:{MAU['nen']};font-family:{PHONG};color:{MAU['chu']}}}
#khung{{width:{rong}px;height:{cao}px;background:{MAU['nen']};display:flex;flex-direction:column;padding:18px 22px 12px}}
.dau{{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px}}
.ma{{font-size:26px;font-weight:700;color:#fff}} .ma small{{font-size:16px;color:{MAU['mo']};font-weight:500;margin-left:8px}}
.gia{{font-size:20px;margin-top:4px}} .gia b{{color:#fff}} .gia span{{color:{mau_doi};margin-left:10px}}
.phai{{text-align:right}} .hieu{{font-size:22px;font-weight:800;color:{MAU['vang']};letter-spacing:.5px}}
.tt{{display:inline-block;margin-top:8px;padding:5px 12px;border-radius:6px;font-size:16px;font-weight:700;
     background:{mau_tt}22;color:{mau_tt};border:1px solid {mau_tt}66}}
#bd{{flex:1;min-height:0}}
.chan{{display:flex;justify-content:space-between;font-size:13px;color:{MAU['mo']};margin-top:6px}}
</style></head><body><div id="khung">
<div class="dau"><div><div class="ma">XAU/USD<small>{html.escape(sl.get('nhan_khung', 'H1 · giờ Việt Nam'))} · {html.escape(ngay)}</small></div>
<div class="gia">{html.escape(sl.get('nhan_dong', 'Đóng phiên trước'))} <b>{_vn(hq['dong'])}</b><span>{'+' if hq['thay_doi'] >= 0 else ''}{_vn(hq['thay_doi'])} ({'+' if hq['thay_doi'] >= 0 else ''}{_vn(hq['thay_doi_pct'])}%)</span></div></div>
<div class="phai"><div class="hieu">TRADINGAUTO</div>{f'<div class="tt">{html.escape(nhan_tt)}</div>' if nhan_tt else ''}</div></div>
<div id="bd"></div>
<div class="chan"><span>tradingauto.org</span><span>Vùng giá để tham khảo, không phải khuyến nghị mua bán</span></div>
</div>
<script>{_js_lwc()}</script>
<script>
const D = {json.dumps(du)};
const L = LightweightCharts;
const c = L.createChart(document.getElementById('bd'), {{
  autoSize: true,
  layout: {{background: {{type: 'solid', color: '{MAU['nen']}'}}, textColor: '{MAU['chu']}', fontSize: 13,
           fontFamily: "{PHONG.replace('"', '')}", panes: {{separatorColor: '{MAU['vien']}'}}}},
  grid: {{vertLines: {{color: '{MAU['luoi']}'}}, horzLines: {{color: '{MAU['luoi']}'}}}},
  rightPriceScale: {{borderColor: '{MAU['vien']}'}},
  timeScale: {{borderColor: '{MAU['vien']}', timeVisible: true, secondsVisible: false, rightOffset: 6,
    tickMarkFormatter: (t, kieu) => {{ const d = new Date(t * 1000), h = n => String(n).padStart(2, '0');
      return kieu <= 2 ? h(d.getUTCDate()) + '/' + h(d.getUTCMonth() + 1) : h(d.getUTCHours()) + ':' + h(d.getUTCMinutes()); }}}},
  localization: {{locale: 'vi-VN', dateFormat: 'dd/MM/yyyy'}},
  crosshair: {{mode: L.CrosshairMode.Hidden}},
  handleScroll: false, handleScale: false,
}});
const s = c.addSeries(L.CandlestickSeries, {{upColor: '{MAU['tang']}', downColor: '{MAU['giam']}', borderVisible: false,
  wickUpColor: '{MAU['tang']}', wickDownColor: '{MAU['giam']}', priceLineColor: '{MAU['vang']}'}});
s.setData(D.nen);
const mauVung = v => v.loai === 'kc' ? '{MAU['giam']}' : (v.loai === 'ht' ? '{MAU['tang']}' : '{MAU['mo']}');
for (const v of D.vung) s.createPriceLine({{price: v.gia, color: mauVung(v), lineWidth: 1,
  lineStyle: L.LineStyle.Dashed, axisLabelVisible: true, title: v.ten}});
if (D.chi_so) {{
  c.addSeries(L.LineSeries, {{color: '#2962ff', lineWidth: 2, priceLineVisible: false, lastValueVisible: false,
    title: 'EMA20'}}).setData(D.ema20);
  c.addSeries(L.LineSeries, {{color: '#e040fb', lineWidth: 2, priceLineVisible: false, lastValueVisible: false,
    title: 'EMA50'}}).setData(D.ema50);
  const r = c.addSeries(L.LineSeries, {{color: '{MAU['vang']}', lineWidth: 2, priceLineVisible: false, title: 'RSI 14'}}, 1);
  r.setData(D.rsi);
  for (const m of [70, 30]) r.createPriceLine({{price: m, color: '{MAU['mo']}', lineWidth: 1, lineStyle: L.LineStyle.Dotted,
    axisLabelVisible: false}});
  c.panes()[1].setHeight(130);
}}
c.timeScale().fitContent();
requestAnimationFrame(() => requestAnimationFrame(() => {{ window.__xong = true; }}));
</script></body></html>"""


def html_bieu_do_tuan(t: Dict[str, Any], chi_so: bool) -> str:
    """Ảnh bài tổng kết tuần: nến D1 ~70 phiên + điểm xoay tuần mới."""
    w, pv = t['nen_tuan'], t['pivot_tuan']
    sl = {'h1': t['d1_ve'], 'ngay_ban_tin': t['ngay_ban_tin'], 'trang_thai': t['dau_tuan_moi'].get('trang_thai'),
          'nhan_khung': 'D1 · tổng kết tuần', 'nhan_dong': 'Đóng tuần',
          'hom_qua': {'dong': w['dong'], 'thay_doi': w['thay_doi'], 'thay_doi_pct': w['thay_doi_pct']},
          'vung': [{'ten': 'R1 tuần', 'gia': pv['R1'], 'loai': 'kc'}, {'ten': 'Pivot tuần', 'gia': pv['P'], 'loai': ''},
                   {'ten': 'S1 tuần', 'gia': pv['S1'], 'loai': 'ht'}]}
    return html_bieu_do(sl, chi_so)


def html_bieu_do_thang(m: Dict[str, Any], chi_so: bool) -> str:
    """Ảnh bài tầm nhìn tháng: nến tuần ~60 tuần + điểm xoay tháng mới."""
    n, pv = m['nen_thang'], m['pivot_thang']
    sl = {'h1': m['w1_ve'], 'ngay_ban_tin': m['ngay_ban_tin'], 'trang_thai': m['dau_thang_moi'].get('trang_thai'),
          'nhan_khung': 'W1 · tầm nhìn tháng', 'nhan_dong': f"Đóng tháng {m['thang_truoc']}",
          'hom_qua': {'dong': n['dong'], 'thay_doi': n['thay_doi'], 'thay_doi_pct': n['thay_doi_pct']},
          'vung': [{'ten': 'R1 tháng', 'gia': pv['R1'], 'loai': 'kc'}, {'ten': 'Pivot tháng', 'gia': pv['P'], 'loai': ''},
                   {'ten': 'S1 tháng', 'gia': pv['S1'], 'loai': 'ht'}]}
    return html_bieu_do(sl, chi_so)


# =====================================================================
# THẺ KẾT QUẢ BOT (phần B)
# =====================================================================

def _o(x: float, don_vi: str = '', so: int = 2) -> str:
    mau = MAU['tang'] if x > 0 else (MAU['giam'] if x < 0 else MAU['mo'])
    return f'<span style="color:{mau}">{"+" if x > 0 else ""}{_vn(x, so)}{don_vi}</span>'


def html_the_ket_qua(kq: Dict[str, Any], rong: int = 1200) -> str:
    e = html.escape
    ngay = datetime.strptime(kq['ngay_nen'], '%Y-%m-%d').strftime('%d/%m/%Y')
    dca, tong = kq.get('dca') or [], kq.get('dca_tong')
    dca_o = ''.join(f"<div class=sc><span>{e(x['ma'])}</span>{_o(x['lai'], ' $')}</div>" for x in dca)
    mo = ''.join(
        f"<tr><td>{e(x['bot'])}</td><td>{e(x['huong'])}</td><td class=p>{_vn(x['vao'])}</td><td>{e(x['gio_vao'])}</td>"
        f"<td class=p>{_o(x['tam_tinh'], ' điểm') if x.get('tam_tinh') is not None else '—'}</td></tr>" for x in kq.get('viptrend_mo') or [])
    gd = kq.get('gia_dong_ngay')
    vt = ''.join(
        f"<tr><td>{e(x['bot'])}</td><td>{e(x['huong'])}</td><td class=p>{_vn(x['vao'])} → {_vn(x['ra'])}</td>"
        f"<td class=p>{_o(x['diem'], ' điểm')}</td><td>{e(x['ly_do'])}</td></tr>" for x in kq['viptrend_dong'])
    return f"""<!doctype html><html><head><meta charset="utf-8"><style>
*{{box-sizing:border-box;margin:0;padding:0}}
body{{background:{MAU['nen']};font-family:{PHONG};color:{MAU['chu']}}}
#khung{{width:{rong}px;background:{MAU['nen']};padding:26px 30px 18px}}
.dau{{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}}
h1{{font-size:28px;color:#fff}} h1 small{{display:block;font-size:15px;color:{MAU['mo']};font-weight:500;margin-top:4px}}
.hieu{{font-size:22px;font-weight:800;color:{MAU['vang']}}}
h2{{font-size:17px;color:#fff;margin:18px 0 8px;padding-left:10px;border-left:4px solid {MAU['vang']}}}
h2 small{{color:{MAU['mo']};font-weight:500;font-size:13px;margin-left:6px}}
table{{width:100%;border-collapse:collapse;font-size:15px}}
th{{text-align:left;color:{MAU['mo']};font-weight:600;padding:6px 8px;border-bottom:1px solid {MAU['vien']}}}
td{{padding:6px 8px;border-bottom:1px solid {MAU['luoi']}}} .p{{text-align:right;font-variant-numeric:tabular-nums}}
th.p{{text-align:right}}
.tong{{font-size:16px;margin:6px 0 0}}
.luoi{{display:grid;grid-template-columns:repeat(5,1fr);gap:8px;font-size:15px}}
.sc{{background:{MAU['luoi']};border-radius:8px;padding:8px 10px;display:flex;justify-content:space-between;flex-wrap:wrap}}
.chan{{font-size:12.5px;color:{MAU['mo']};margin-top:16px;display:flex;justify-content:space-between}}
.trong{{color:{MAU['mo']};font-size:15px}}
</style></head><body><div id="khung">
<div class="dau"><h1>{e(kq.get('tieu_de') or ('Kết quả bot ngày ' + ngay))}<small>{e(kq['khung'])} · đăng cả ngày lãi lẫn ngày lỗ</small></h1>
<div class="hieu">TRADINGAUTO</div></div>
<h2>SMART DCA — lãi/lỗ {'cả tuần' if kq.get('tieu_de') else 'trong ngày'} của 10 mã signal<small>kết quả perday · sổ tiêu chuẩn, vốn danh nghĩa $10.000/mã · không phải tài khoản thật</small></h2>
{f'<div class="luoi">{dca_o}</div><div class="tong">Tổng {tong["so_ma"]} mã: {_o(tong["lai"], " $")} · {tong["so_ma_lai"]} mã lãi, {tong["so_ma_lo"]} mã lỗ</div>' if dca else '<div class="trong">Chưa có kết quả perday của ngày này.</div>'}
<h2>VipTrend — lệnh đã đóng<small>tính theo điểm giá</small></h2>
{f'<table><tr><th>Bot</th><th>Hướng</th><th class=p>Vào → Ra</th><th class=p>Kết quả</th><th>Lý do</th></tr>{vt}</table>' if vt else '<div class="trong">Không có lệnh nào đóng trong khung này.</div>'}
<h2>VipTrend — lệnh đang mở {'qua cuối tuần' if kq.get('tieu_de') else 'cuối ngày'}<small>tạm tính theo điểm giá{(' tại giá đóng cửa ' + _vn(gd)) if gd is not None else ''} · chưa chốt</small></h2>
{f'<table><tr><th>Bot</th><th>Hướng</th><th class=p>Giá vào</th><th>Vào lúc (giờ sàn)</th><th class=p>Tạm tính</th></tr>{mo}</table>' if mo else '<div class="trong">Không có lệnh nào đang mở.</div>'}
<div class="chan"><span>tradingauto.org · /pages/dca_signals · /pages/signal_stream</span><span>Kết quả quá khứ không bảo đảm kết quả tương lai</span></div>
</div><script>window.__xong = true;</script></body></html>"""
