# Key Sync Service - Bộ chuyển đổi bản ghi theo từng máy chủ key
#
# Website trả về MỘT dạng bản ghi chung cho mọi sản phẩm. Nhưng năm máy
# chủ key trên server đọc năm dạng khác nhau — khác tên trường, khác ký
# hiệu "vĩnh viễn", khác cả chỗ đặt tên tài khoản. Ghi sai một chỗ là
# key không dùng được mà không ai biết cho tới khi khách kêu.
#
# Mọi thứ dưới đây đối chiếu từ mã nguồn máy chủ key (16/09/2026):
#   routes/quangkey.py     RAW{} và TOOLS[]  — template từng backend
#   routes/copytrade.py    ct_is_expired, CT_PERMANENT_EXPIRED
#   routes/proxy.py        PXlicense
#   routes/telelicense.py  _expired, _auth_lic
#   routes/dca.py          DCAlicense
#
# HAI CÁI BẪY đã tránh ở đây:
#
# 1. PXlicense và telelicense dùng một trường để phân vai. Ở PXlicense là
#    `username`, ở telelicense là `role`. Giá trị phải đúng chữ 'user'
#    thì mới bị đếm phiên; giá trị khác (kể cả tên khách) được BỎ QUA
#    giới hạn:
#       proxy.py:148   role = lic.get('username', 'user')
#                      if is_demo or role != 'user': ... bypass
#    Ghi tên tài khoản của khách vào đó là biếu không mỗi licence số
#    phiên vô hạn. Tên khách để ở `note`.
#
# 2. Ký hiệu "vĩnh viễn" khác nhau:
#       keymaster    '' hoặc 'vv'      (CT_PERMANENT_EXPIRED)
#       telelicense  '' ở trường `expires` (chứ không phải `expired`)
#       PXlicense    không xét hạn, chỉ xét `status`
#       DCAlicense   '' ở `expired`
#       keytdv       '' ở `expired`
#    Trước đây bản đồng bộ ghi '2100-01-01' cho mọi loại. Chạy được
#    nhưng sai quy ước, và với telelicense thì ghi nhầm hẳn tên trường.


def _han(rec, truong='expired', vinh_vien=''):
    """Giá trị hạn dùng cho một máy chủ key.

    `vinh_vien` là ký hiệu mà máy chủ đó hiểu là không bao giờ hết hạn.
    """
    if str(rec.get('forever', '0')) == '1':
        return {truong: vinh_vien}
    return {truong: rec.get('expired', '')}


def _so_luong(rec, mac_dinh=1):
    try:
        n = int(rec.get('number') or mac_dinh)
    except (TypeError, ValueError):
        n = mac_dinh
    return max(1, n)


# ---------------------------------------------------------------------
# keytdv — TradingView (Redis db 5)
#   quangkey RAW['tradingview'].template:
#     {expired, name, active_clients, username}
#   `active_clients` do server tự ghi -> KHÔNG đụng vào.
# ---------------------------------------------------------------------
def _keytdv(rec):
    out = {'username': rec.get('username', '')}
    out.update(_han(rec, 'expired', ''))
    return out


# ---------------------------------------------------------------------
# keymaster — Copytrade (Redis db 3)
#   quangkey RAW['copytrade'].template:
#     {master_code, username, masterslave, master, slave, expired}
#   `masterslave`, `master`, `slave` do server ghi khi máy khách kết nối
#   -> KHÔNG đụng vào, nếu không là xoá sạch danh sách slave đang chạy.
#
#   GHI CHÚ: `number` (số slave) VẪN ghi để trang quản trị và báo cáo đọc
#   được, nhưng máy chủ KHÔNG còn cưỡng chế nó:
#     copytrade.py:652  "BO chan 'number_slave > 1': khong con gioi han
#                        so slave."
#   Nghĩa là website đang bán thêm slave mà server không chặn. Đây là
#   việc phải chốt lại, không phải chuyện bộ chuyển đổi này sửa được.
# ---------------------------------------------------------------------
def _keymaster(rec):
    out = {
        'username':    rec.get('username', ''),
        'master_code': rec.get('master_code', ''),
        'number':      str(_so_luong(rec)),
    }
    out.update(_han(rec, 'expired', 'vv'))
    return out


# ---------------------------------------------------------------------
# PXlicense — Trade Panel (Redis db 4)
#   proxy.py:8  {expired, status, max_accounts, username, note}
#   Không xét hạn theo ngày ("tool nay khong co han su dung"), chỉ xét
#   `status`. Vẫn ghi `expired` cho trang quản trị nhìn được.
#   `username` PHẢI là 'user' — xem bẫy số 1 ở đầu file.
# ---------------------------------------------------------------------
def _px(rec):
    out = {
        'status':       'active',
        'username':     'user',
        'max_accounts': _so_luong(rec),
        'note':         'web:' + rec.get('username', ''),
    }
    out.update(_han(rec, 'expired', ''))
    return out


# ---------------------------------------------------------------------
# telelicense — SignalTrader / SignalBroadcaster (Redis db 4)
#   telelicense.py: {status, role, pass, max_accounts, expires, bound,
#                    note, created}
#   - hạn dùng ở `expires`, KHÔNG phải `expired`; rỗng = vĩnh viễn
#   - `pass` bắt buộc: _auth_lic so sánh thẳng, rỗng nghĩa là ai biết key
#     cũng dùng được
#   - `role` phải là 'user' mới bị đếm tài khoản
#   - `bound` do server ghi khi máy khách gắn tài khoản -> KHÔNG đụng vào
# ---------------------------------------------------------------------
def _tele(rec):
    out = {
        'status':       'active',
        'role':         'user',
        'pass':         rec.get('pass', ''),
        'max_accounts': _so_luong(rec),
        'note':         'web:' + rec.get('username', ''),
    }
    out.update(_han(rec, 'expires', ''))
    return out


# ---------------------------------------------------------------------
# DCAlicense — DCA bot (Redis db 4)
#   quangkey RAW['dca'].template:
#     {expired, owner, username, tg_id, tg_name, created_by,
#      active_clients, account, mt5_id}
#   dca.py:294  username là TÊN THẬT; chỉ 'admin'/'demo' mới bypass
#   -> ghi tên khách vào đây là đúng, khác hẳn PXlicense.
#   `active_clients`, `last_seen`, `action` do server ghi -> KHÔNG đụng.
# ---------------------------------------------------------------------
def _dca(rec):
    out = {
        'username':   rec.get('username', ''),
        'owner':      rec.get('username', ''),
        'created_by': 'web',
    }
    out.update(_han(rec, 'expired', ''))
    return out


# Khoá là TÊN HASH, vì cùng một hash thì cùng một dạng bản ghi.
# telelicense:<sp> dùng chung bộ chuyển đổi với telelicense.
# ---------------------------------------------------------------------
# MT5Mlicense — MT5 Manager (Redis db 4)
#   routes/mt5manager.py:_lookup đọc {pass, status, expired, max_accounts}
#
# ⚠ NĂM TRƯỜNG SERVER TỰ GHI — adapter KHÔNG được đụng vào:
#     slug            endpoint khách tự đăng ký (hash MT5Mslug giữ tính duy
#                     nhất). Ghi đè là khách mất đường truy cập từ xa mỗi 120
#                     giây, mà nhìn log đồng bộ thì thấy "bình thường".
#     last_seen  last_ip  last_version  last_machine     nhịp tim ghi.
#   SyncEngine MERGE chứ không thay thế (sync_engine.py:135
#   `current_dict.update(v)`), nên chỉ cần KHÔNG liệt kê ra đây là chúng sống.
#
# ⚠ `max_accounts` ở đây là SỐ MÁY CHẠY CÙNG LÚC, không phải số tài khoản MT5:
#     mt5manager.py:202   limit = int(lic.get('max_accounts') or 1) or 1
#   rồi so với danh sách máy đang hoạt động. Ghi số tài khoản MT5 vào đây là
#   phá luôn cơ chế một-key-một-máy. Suất free = 1.
#
# ⚠ Tên khách để ở `note`, KHÔNG để ở `username`: SyncEngine coi
#   username == 'admin' là "đừng đụng tới" (sync_engine.py:117 và :131).
#   MT5Mlicense không có trường phân vai nào cả, nên `username` ở đây vô dụng
#   mà lại dễ gây nhầm với PXlicense/telelicense.
# ---------------------------------------------------------------------
def _mt5m(rec):
    out = {
        'pass':         rec.get('pass', ''),
        'status':       rec.get('status', 'active'),
        'max_accounts': _so_luong(rec),        # SỐ MÁY, xem ghi chú trên
        'note':         rec.get('username', '') or rec.get('note', ''),
        # Tên hiển thị trên thanh đầu của tool. Ưu tiên email, không có thì
        # username. Tool CHE BỚT khi hiện (qu***@gmail.com) nên để nguyên ở đây.
        'email':        rec.get('email', ''),
    }
    out.update(_han(rec, 'expired', 'vv'))     # 'vv' = vĩnh viễn, khớp _is_expired
    return out


BO_CHUYEN_DOI = {
    'keytdv':      _keytdv,
    'keymaster':   _keymaster,
    'PXlicense':   _px,
    'telelicense': _tele,
    'DCAlicense':  _dca,
    'MT5Mlicense': _mt5m,
}


def chuyen_doi(key_hash, rec):
    """Đổi bản ghi chung của website sang đúng dạng của một hash Redis.

    Không biết hash đó thì trả về None — gọi bên ngoài phải bỏ qua kèm
    cảnh báo, chứ đừng ghi dạng chung vào một máy chủ không hiểu nó.
    """
    ten = str(key_hash or '')
    if ten.startswith('telelicense'):      # telelicense, telelicense:sb, ...
        ten = 'telelicense'
    ham = BO_CHUYEN_DOI.get(ten)
    if ham is None:
        return None
    return ham(rec)


def truong_cua(key_hash):
    """Danh sách trường mà bộ chuyển đổi này ghi.

    SyncEngine dùng danh sách này để so sánh cũ/mới. Chỉ liệt kê trường
    MÌNH ghi — trường nào server tự ghi (bound, active_clients,
    last_seen, masterslave...) phải nằm ngoài, nếu không mỗi vòng đồng
    bộ lại thấy "khác" rồi ghi đè mất trạng thái đang chạy.
    """
    mau = chuyen_doi(key_hash, {'forever': '0', 'expired': '', 'username': '',
                                'master_code': '', 'number': '1', 'pass': ''})
    return sorted(mau.keys()) if mau else None
