# -*- coding: utf-8 -*-
"""WORKER Agent-Lab — lấy việc trong hàng đợi lab.db, chạy 2 bước AGY rồi xuất DOCX.

Cách dùng:
   python worker_lab.py            # chạy liên tục (vòng lặp, quét mỗi 15 giây)
   python worker_lab.py --mot-lan  # xử lý 1 việc rồi thoát (để chạy thử)
"""
import json, os, re, shutil, sqlite3, subprocess, sys, time, traceback

GOC = r'C:\Claw 2\Nghien cuu\Agent-Lab'
DB = os.path.join(GOC, 'lab.db')
VIEC = os.path.join(GOC, 'viec')
KIEN_THUC = os.path.join(GOC, 'kien_thuc', 'CSDL_KIEN_THUC_ABC.md')
MD2DOCX = r'C:\Claw 2\QC\_md2docx.py'
MAIL_SCRIPT = os.path.join(os.path.expandvars(r'%LOCALAPPDATA%'), 'hermes', 'skills', 'email',
                           'gui-email-kem-file', 'scripts', 'gui_mail_kem_file.py')
URL_PUBLIC = 'https://desktop-9gsdkd2.tail0ffe94.ts.net/lab/'
AGY = r'C:\Users\admin\AppData\Local\agy\bin\agy.exe'
PROJECT = '2f54d0c6-f33f-4dae-b5b7-840339496ec7'      # Du-an-A-Quy-trinh-son
MODEL = 'gemini-3.1-pro-high'
PY = sys.executable
os.makedirs(VIEC, exist_ok=True)

SCHEMA = '''
CREATE TABLE IF NOT EXISTS viec (
  id INTEGER PRIMARY KEY AUTOINCREMENT, tao_luc TEXT, bat_dau TEXT, ket_thuc TEXT,
  trang_thai TEXT DEFAULT 'cho', nguoi_gui TEXT, loai_go TEXT, dac_tinh TEXT, yeu_cau TEXT,
  do_bong TEXT, ghi_chu TEXT, thu_muc TEXT, anh_moc TEXT, anh_mau TEXT, email TEXT,
  email_luc TEXT, email_loi TEXT, phan_tich TEXT, ket_qua TEXT, docx TEXT,
  loi TEXT, giay REAL);
CREATE TABLE IF NOT EXISTS phan_hoi (
  id INTEGER PRIMARY KEY AUTOINCREMENT, viec_id INTEGER, ket_qua_thu TEXT, ghi_chu TEXT, luc TEXT);
'''

# cột thêm sau này — tự nâng cấp DB đang dùng, không mất dữ liệu
COT_THEM = (('email', 'TEXT'), ('email_luc', 'TEXT'), ('email_loi', 'TEXT'))


def noi_db():
    con = sqlite3.connect(DB, timeout=30)
    con.executescript(SCHEMA)
    co = [r[1] for r in con.execute('PRAGMA table_info(viec)')]
    for cot, kieu in COT_THEM:
        if cot not in co:
            con.execute('ALTER TABLE viec ADD COLUMN %s %s' % (cot, kieu))
    con.commit()
    return con


def ghi_log(d, ten, nd):
    open(os.path.join(d, ten), 'w', encoding='utf-8').write(nd)


def chay_agy(d, prompt, out_file, ten_log):
    cmd = [AGY, '-p', prompt, '--model', MODEL, '--effort', 'high', '--project', PROJECT,
           '--add-dir', d, '--dangerously-skip-permissions', '--output-format', 'json', '--print-timeout', '0']
    t0 = time.time()
    p = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='replace', cwd=d)
    dt = time.time() - t0
    try:
        j = json.loads(p.stdout or '{}')
        tt = json.dumps(j.get('usage'), ensure_ascii=False)
    except Exception:
        j, tt = {}, '(không parse json)'
    ghi_log(d, ten_log, '=== %.1f s | rc=%s | model=%s\nusage=%s\n--- stdout (đầu) ---\n%s\n--- stderr (cuối) ---\n%s'
            % (dt, p.returncode, MODEL, tt, (p.stdout or '')[:1500], (p.stderr or '')[-1500:]))
    ton_tai = os.path.exists(out_file)
    return dt, ton_tai, (os.path.getsize(out_file) if ton_tai else 0)


def prompt_b1(v, d):
    return ('Bạn là chuyên gia phân tích màu gỗ bằng thị giác. Hãy TỰ NHÌN 2 ảnh trong thư mục "%s\\anh": '
            '"moc.jpg" = ảnh GỖ MỘC (phôi trước khi sơn), "mau.jpg" = ảnh MẪU MÀU cần đạt (đã sơn xong).\n\n'
            'Thông tin Lab nhập: loại gỗ/nền = "%s"; đặc tính gỗ = "%s"; yêu cầu chất lượng = "%s"; độ bóng yêu cầu = "%s"; ghi chú = "%s".\n\n'
            'Viết DUY NHẤT một tệp "%s\\PHAN_TICH.md" (UTF-8, tiếng Việt, gọn, có bảng), gồm 4 phần:\n'
            'A. GỖ MỘC: nhận diện loài/nền gỗ nhìn thấy (nêu độ tin cậy %%), mạch gỗ mở/kín, độ đồng đều thớ, vân (thẳng/xoáy/ghim sớ), '
            'dấu hiệu dầu–nhựa/tanin, tông màu nền (ước lượng dải L*a*b*), rủi ro khi nhuộm (loang đốm, cháy màu ở rãnh, hút không đều).\n'
            'B. MẪU MÀU: tông màu chủ đạo, độ sáng chủ đạo, tương phản vân, mức bóng nhìn thấy, ước lượng dải L*a*b* vùng màu thực, '
            'dấu hiệu đọng bột trắng/đục (nếu có thì màu KHÔNG phải thuần nhuộm).\n'
            'C. KẾT LUẬN QUAN TRỌNG NHẤT: màu này là DYE (nhuộm ngấm) hay PIGMENT (màu màng) hay DYE + PIGMENT — nêu %% và bằng chứng nhìn thấy.\n'
            'D. HƯỚNG XỬ LÝ MÀU ĐỂ ĐẠT MẪU: loại nền cần có, có cần lớp chặn trước (chống loang/ngăn dầu) hay không, số lớp màu dự kiến, '
            'và NHỮNG GÌ KHÔNG THỂ KẾT LUẬN TỪ ẢNH (độ bóng thật, độ thấm, độ ẩm gỗ, độ bám dính…).\n\n'
            'Tuyệt đối KHÔNG bịa số đo; chỗ nào không chắc thì ghi rõ mức tin cậy. KHÔNG viết quy trình sơn ở bước này. '
            'Kết thúc tệp bằng dòng HOAN TAT.' % (d, v['loai_go'], v['dac_tinh'], v['yeu_cau'], v['do_bong'], v['ghi_chu'], d))


def prompt_b2(v, d):
    return ('Bạn là kỹ thuật viên sơn gỗ cao cấp của một nhà máy sơn. Nhiệm vụ: từ kết quả phân tích ảnh và gói kiến thức nội bộ, '
            'lên PHƯƠNG ÁN QUY TRÌNH PHỦ SƠN và CÔNG THỨC PHA MÀU cho từng công đoạn.\n\n'
            'Đọc: (1) kết quả phân tích ảnh của bạn: "%s\\PHAN_TICH.md"; (2) GÓI KIẾN THỨC 3 KHO (A quy trình + B màu + D vật liệu/yêu cầu chất lượng): '
            '"%s" — bắt buộc tuân thủ phần A1 (luật), dùng SỐ BƯỚC + TÊN CHUẨN ở phần A2 khi mô tả công đoạn, chỉ dùng mã màu có trong phần B1/B2, '
            'và áp phần D1–D4 (loài gỗ, map bước, yêu cầu chất lượng, hiệu chỉnh theo độ hút màu).\n\n'
            'Thông tin Lab nhập: loại gỗ/nền = "%s"; đặc tính gỗ = "%s"; yêu cầu chất lượng = "%s"; độ bóng yêu cầu = "%s"; ghi chú = "%s".\n\n'
            'QUAN TRỌNG — 2 ảnh đầu vào ĐÃ CÓ SẴN trong thư mục: "%s\\anh\\moc.jpg" (gỗ mộc) và "%s\\anh\\mau.jpg" (mẫu màu). '
            'Hãy tự đọc 2 tệp ảnh đó bằng thị giác. TUYỆT ĐỐI KHÔNG viết câu "thiếu ảnh gỗ mộc" hay "đề nghị cung cấp ảnh" — ảnh đã có đủ.\n\n'
            'Viết DUY NHẤT một tệp "%s\\QUY_TRINH_VA_CONG_THUC.md" (UTF-8, tiếng Việt, GỌN ≤ 2 trang A4, đúng Hợp đồng đầu ra ở phần E của gói kiến thức), gồm:\n'
            'PHẦN 1 — BẢNG QUY TRÌNH: đúng 2 phương án (PA1 khuyến nghị, PA2 thay thế). Mỗi PA 1 bảng chỉ gồm các công đoạn PHỦ SƠN; cột: '
            'Công đoạn (số bước chuẩn + tên chuẩn) | Mục đích | Cách làm | Định lượng | Khô/chờ. KHÔNG ghi tên thương mại/mã sản phẩm trong bảng này. '
            'Sau MỖI lần chà nhám phải có dòng "KIỂM TRA MÀU – DẶM MÀU NẾU LÁC". Phân loại màu theo 4 nhóm (không màu / có màu pigment / trong suốt / khác).\n'
            'PHẦN 2 — CÔNG THỨC PHA MÀU: bảng riêng cho TỪNG công đoạn có màu (mã · tỉ lệ %% · vai trò), cách pha theo thứ tự, thời gian khuấy, yêu cầu lọc, '
            'và bảng 4 dòng chỉnh lệch (đỏ/rực, ngả lạnh-xám, quá tối, loang vân). Ưu tiên DYE; chỉ dùng mã CÓ TỒN KHO, mã thiếu thì ghi rõ và đề xuất mã thay thế.\n'
            'PHẦN 3 — CẦN LÀM THẺ MẪU XÁC NHẬN: các ô thử (nồng độ/số lớp), tiêu chí đạt (GU, màu so mẫu, bám dính), và những gì không thể kết luận từ ảnh.\n\n'
            'Không viết lan man, không nhắc lại phần phân tích ảnh. Kết thúc tệp bằng dòng HOAN TAT.'
            % (d, KIEN_THUC, v['loai_go'], v['dac_tinh'], v['yeu_cau'], v['do_bong'], v['ghi_chu'], d, d, d))


def gui_mail(v, docx):
    """Gửi kết quả (DOCX đính kèm) tới email người gửi — dùng Gmail API sẵn có. Trả (ok, thông báo)."""
    to = (v.get('email') or '').strip()
    if not to or '@' not in to:
        return False, 'không có email'
    if not os.path.exists(MAIL_SCRIPT):
        return False, 'thiếu script gửi mail: %s' % MAIL_SCRIPT
    tieu_de = 'Agent-Lab — Đề nghị quy trình & công thức pha màu (việc #%s · %s)' % (v['id'], v['loai_go'])
    than = ('Kết quả đề nghị từ Agent-Lab (phòng Lab) — việc #%s\n\n'
            'Người gửi: %s\nLoại gỗ/nền: %s\nĐặc tính gỗ: %s\nYêu cầu chất lượng: %s · Độ bóng: %s\nGhi chú: %s\n\n'
            'File Word đính kèm gồm: 2 ảnh đầu vào · PHẦN 1 bảng quy trình (2 phương án, kèm dòng kiểm tra–dặm màu sau mỗi lần chà nhám) · '
            'PHẦN 2 công thức pha màu từng công đoạn · PHẦN 3 các ô cần làm thẻ mẫu xác nhận · phụ lục phân tích ảnh.\n\n'
            'Xem lại trên web: %sket-qua?id=%s\n\n'
            'LƯU Ý: đây là ĐỀ NGHỊ của Agent-Lab, KHÔNG thay thế thẻ mẫu xác nhận của KTV. Công thức chỉ dùng mã có tồn kho trong CSDL B.\n'
            % (v['id'], v['nguoi_gui'], v['loai_go'], v['dac_tinh'], v['yeu_cau'], v['do_bong'], v['ghi_chu'] or '—', URL_PUBLIC, v['id']))
    p = subprocess.run([PY, MAIL_SCRIPT, '--to', to, '--subject', tieu_de, '--body-text', than,
                        '--attach', docx],
                       capture_output=True, text=True, encoding='utf-8', errors='replace', cwd=GOC)
    ra = ((p.stdout or '') + (p.stderr or '')).strip()
    ghi_log(os.path.dirname(docx), '_log_mail.txt', ra[:4000])
    id_thu = ''
    m = re.search(r'"?id"?\s*[:=]\s*"?([0-9a-f]{12,})', ra)
    if m:
        id_thu = m.group(1)
    if p.returncode == 0 and ('đã gửi' in ra.lower() or 'sent' in ra.lower() or id_thu):
        return True, 'đã gửi tới %s%s' % (to, (' (id %s)' % id_thu) if id_thu else '')
    return False, 'lỗi gửi mail: %s' % ra[-300:]


def xu_ly(v):
    d = v['thu_muc']
    os.makedirs(os.path.join(d, 'anh'), exist_ok=True)
    demo = v['bat_dau']
    con = noi_db()
    con.execute("UPDATE viec SET trang_thai='dang_chay', bat_dau=? WHERE id=?", (demo, v['id']))
    con.commit()
    try:
        # sao chép ảnh vào thư mục việc (tên ASCII)
        for cot, ten in (('anh_moc', 'moc.jpg'), ('anh_mau', 'mau.jpg')):
            nguon = v.get(cot)
            if nguon and os.path.exists(nguon):
                shutil.copy2(nguon, os.path.join(d, 'anh', ten))
        thieu = [t for t in ('moc.jpg', 'mau.jpg') if not os.path.exists(os.path.join(d, 'anh', t))]
        p1 = os.path.join(d, 'PHAN_TICH.md')
        p2 = os.path.join(d, 'QUY_TRINH_VA_CONG_THUC.md')
        t1, ok1, s1 = chay_agy(d, prompt_b1(v, d), p1, '_log_b1.txt')
        if not ok1 and thieu:
            raise RuntimeError('Bước 1 không tạo được kết quả (thiếu ảnh: %s)' % ', '.join(thieu))
        t2, ok2, s2 = chay_agy(d, prompt_b2(v, d), p2, '_log_b2.txt')
        if not ok2:
            raise RuntimeError('Bước 2 không tạo được kết quả quy trình/công thức')
        # gộp thành 1 tệp để xuất DOCX (kèm 2 ảnh đầu vào)
        kq = os.path.join(d, 'KET_QUA.md')
        md = ['# PHƯƠNG ÁN QUY TRÌNH SƠN & CÔNG THỨC PHA MÀU',
              '', '**Người gửi:** %s · **Ngày:** %s' % (v['nguoi_gui'], time.strftime('%d/%m/%Y %H:%M')),
              '', '**Loại gỗ/nền:** %s · **Yêu cầu chất lượng:** %s · **Độ bóng:** %s' % (v['loai_go'], v['yeu_cau'], v['do_bong']),
              '', '**Đặc tính gỗ:** %s' % v['dac_tinh'], '', '**Ghi chú:** %s' % (v['ghi_chu'] or '—'),
              '', '## Ảnh đầu vào', '', '![Ảnh gỗ mộc](anh/moc.jpg)', '', '![Ảnh mẫu màu](anh/mau.jpg)',
              '', '---', '', open(p2, encoding='utf-8').read().strip(), '', '---', '',
              '# PHỤ LỤC — PHÂN TÍCH ẢNH CỦA AGY', '', open(p1, encoding='utf-8').read().strip(),
              '', '> **Lưu ý:** Đây là ĐỀ NGHỊ của Lab/agent, KHÔNG thay thế thẻ mẫu màu xác nhận của KTV.',
              '> Công thức chỉ dùng mã có trong CSDL B; mã thiếu tồn kho đã ghi rõ.', '']
        ghi_log(d, 'KET_QUA.md', '\n'.join(md))
        docx = os.path.join(d, 'KET_QUA.docx')
        r = subprocess.run([PY, MD2DOCX, kq, docx, d], capture_output=True, text=True,
                           encoding='utf-8', errors='replace', cwd=d)
        if not os.path.exists(docx):
            raise RuntimeError('Xuất DOCX thất bại: %s' % ((r.stdout or '') + (r.stderr or ''))[-300:])
        con.execute("UPDATE viec SET trang_thai='xong', ket_thuc=?, phan_tich=?, ket_qua=?, docx=?, giay=? WHERE id=?",
                    (time.strftime('%Y-%m-%d %H:%M:%S'), p1, p2, docx, round(t1 + t2, 1), v['id']))
        con.commit()
        # gửi email kết quả (nếu Lab có điền) — lỗi mail KHÔNG làm hỏng việc
        ok_mail, tb_mail = (False, 'không có email')
        if (v.get('email') or '').strip():
            ok_mail, tb_mail = gui_mail(v, docx)
            con.execute('UPDATE viec SET email_luc=?, email_loi=? WHERE id=?',
                        (time.strftime('%Y-%m-%d %H:%M:%S') if ok_mail else None, '' if ok_mail else tb_mail, v['id']))
            con.commit()
        return 'xong', 'B1 %.0fs + B2 %.0fs | mail: %s' % (t1, t2, tb_mail)
    except Exception as e:
        con.execute("UPDATE viec SET trang_thai='loi', ket_thuc=?, loi=? WHERE id=?",
                    (time.strftime('%Y-%m-%d %H:%M:%S'), ('%s\n%s' % (e, traceback.format_exc()[-800:])), v['id']))
        con.commit()
        return 'loi', str(e)


def lay_viec():
    con = noi_db()
    r = con.execute("SELECT id,nguoi_gui,loai_go,dac_tinh,yeu_cau,do_bong,ghi_chu,thu_muc,anh_moc,anh_mau,email FROM viec "
                    "WHERE trang_thai='cho' ORDER BY id LIMIT 1").fetchone()
    if not r:
        return None
    cot = ('id', 'nguoi_gui', 'loai_go', 'dac_tinh', 'yeu_cau', 'do_bong', 'ghi_chu', 'thu_muc', 'anh_moc', 'anh_mau', 'email')
    v = dict(zip(cot, r))
    v['bat_dau'] = time.strftime('%Y-%m-%d %H:%M:%S')
    return v


def main():
    mot_lan = '--mot-lan' in sys.argv
    print('WORKER Agent-Lab bắt đầu %s | model %s%s' % (time.strftime('%H:%M:%S'), MODEL, ' (1 việc)' if mot_lan else ''))
    while True:
        v = lay_viec()
        if not v:
            if mot_lan:
                print('không có việc chờ')
                return
            time.sleep(15)
            continue
        print('→ việc #%s của %s | %s' % (v['id'], v['nguoi_gui'], v['loai_go']))
        tt, mo = xu_ly(v)
        print('   kết quả: %s — %s' % (tt, mo))
        if mot_lan:
            return


if __name__ == '__main__':
    main()
