# -*- coding: utf-8 -*-
"""
vault.py — ชิ้นแรกของงานจริง (LINE Switcher): ที่เก็บรหัสแบบ "เข้ารหัส ต่อเครื่อง ต่อคน"
────────────────────────────────────────────────────────────────────────
ใช้ Windows DPAPI (ติดมากับวินโดวส์อยู่แล้ว — ไม่ต้องลงไลบรารีเพิ่ม):
  • เข้ารหัสด้วยกุญแจของ "บัญชีวินโดวส์ที่กำลังรัน" → ไฟล์ที่ได้ถอดได้เฉพาะบัญชีนั้น บนเครื่องนั้น
  • ก๊อปไฟล์ไปเครื่องอื่น / เปิดจากวินโดวส์ยูสเซอร์อื่น = ถอดไม่ได้ (กันไฟล์หลุดได้ดีระดับนึง)
  • ไม่มีมาสเตอร์พาสให้จำ — สะดวกสุดโดยไม่ต้องเก็บ plaintext
ข้อจำกัดตรง ๆ: กันได้ระดับ "คนอื่นบนเครื่อง / ไฟล์หายไปเครื่องอื่น" —
               ไม่ได้กันมัลแวร์ที่รันเป็นผู้ใช้คนเดียวกัน (อะไรในเครื่องก็กันไม่ได้ 100%)

ที่เก็บจริง (ตอนสร้างเสร็จ): %LOCALAPPDATA%\\line-switcher\\vault.bin
  — อยู่นอกโฟลเดอร์ที่ sync คลาวด์ · ไม่ขึ้น git · รหัสไม่เคยออกจากเครื่อง

ทดสอบตัวเอง:  python vault.py selftest
"""
import ctypes
import ctypes.wintypes
import json
import os
import pathlib
import sys

# ── DPAPI (Crypt32) ─────────────────────────────────────────────────────
class DATA_BLOB(ctypes.Structure):
    _fields_ = [("cbData", ctypes.wintypes.DWORD),
                ("pbData", ctypes.POINTER(ctypes.c_char))]


_crypt32 = ctypes.windll.crypt32
_kernel32 = ctypes.windll.kernel32

_crypt32.CryptProtectData.argtypes = [
    ctypes.POINTER(DATA_BLOB), ctypes.c_wchar_p, ctypes.POINTER(DATA_BLOB),
    ctypes.c_void_p, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(DATA_BLOB)]
_crypt32.CryptProtectData.restype = ctypes.wintypes.BOOL
_crypt32.CryptUnprotectData.argtypes = [
    ctypes.POINTER(DATA_BLOB), ctypes.POINTER(ctypes.c_wchar_p), ctypes.POINTER(DATA_BLOB),
    ctypes.c_void_p, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(DATA_BLOB)]
_crypt32.CryptUnprotectData.restype = ctypes.wintypes.BOOL
_kernel32.LocalFree.argtypes = [ctypes.c_void_p]
_kernel32.LocalFree.restype = ctypes.c_void_p


def _to_blob(data: bytes) -> DATA_BLOB:
    buf = ctypes.create_string_buffer(data, len(data))
    return DATA_BLOB(len(data), ctypes.cast(buf, ctypes.POINTER(ctypes.c_char)))


def _from_blob(blob: DATA_BLOB) -> bytes:
    return ctypes.string_at(blob.pbData, blob.cbData)


def protect(data: bytes) -> bytes:
    """เข้ารหัส bytes → blob (ผูกกับบัญชีวินโดวส์ปัจจุบัน)"""
    bin_blob = _to_blob(data)
    out = DATA_BLOB()
    ok = _crypt32.CryptProtectData(ctypes.byref(bin_blob), "line-switcher", None,
                                   None, None, 0, ctypes.byref(out))
    if not ok:
        raise OSError("CryptProtectData ล้มเหลว")
    try:
        return _from_blob(out)
    finally:
        _kernel32.LocalFree(ctypes.cast(out.pbData, ctypes.c_void_p))


def unprotect(blob: bytes) -> bytes:
    """ถอดรหัส blob → bytes (ใช้ได้เฉพาะเครื่อง/บัญชีวินโดวส์เดิม)"""
    bin_blob = _to_blob(blob)
    out = DATA_BLOB()
    ok = _crypt32.CryptUnprotectData(ctypes.byref(bin_blob), None, None,
                                     None, None, 0, ctypes.byref(out))
    if not ok:
        raise OSError("CryptUnprotectData ล้มเหลว (ไฟล์นี้เป็นของบัญชี/เครื่องอื่น?)")
    try:
        return _from_blob(out)
    finally:
        _kernel32.LocalFree(ctypes.cast(out.pbData, ctypes.c_void_p))


# ── ที่เก็บบัญชี ─────────────────────────────────────────────────────────
STORE = pathlib.Path(os.environ.get("LOCALAPPDATA", ".")) / "line-switcher" / "vault.bin"


def save_accounts(accounts: list) -> None:
    """เก็บทั้งชุด (list ของ dict) แบบเข้ารหัส"""
    raw = json.dumps(accounts, ensure_ascii=False).encode("utf-8")
    STORE.parent.mkdir(parents=True, exist_ok=True)
    STORE.write_bytes(protect(raw))


def load_accounts() -> list:
    if not STORE.exists():
        return []
    return json.loads(unprotect(STORE.read_bytes()).decode("utf-8"))


# ── ทดสอบตัวเอง ──────────────────────────────────────────────────────────
def selftest() -> int:
    print("== vault.py selftest ==")
    demo = [{"nick": "บัญชีทดสอบ", "email": "demo@mail.com",
             "password": "P@ssw0rd-ทดสอบ", "phone": "เครื่องทดสอบ"}]
    save_accounts(demo)
    got = load_accounts()
    ok_roundtrip = got == demo

    blob = STORE.read_bytes()
    leaked = [s for s in ("P@ssw0rd", "demo@mail.com", "บัญชีทดสอบ")
              if s.encode("utf-8") in blob]

    print("1) เข้ารหัส → ถอดกลับได้เหมือนเดิม   :", "ผ่าน" if ok_roundtrip else "ไม่ผ่าน")
    print("2) ไฟล์ดิบไม่มีข้อความจริงหลงเหลือ    :", "ผ่าน" if not leaked else ("ไม่ผ่าน " + str(leaked)))
    print("   →", STORE)
    print("   → ขนาด", len(blob), "ไบต์ · หน้าตาไฟล์ดิบ:", blob[:10].hex(), "...")
    STORE.unlink(missing_ok=True)
    print("3) เก็บกวาดไฟล์ทดสอบแล้ว")
    return 0 if (ok_roundtrip and not leaked) else 1


if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "selftest":
        sys.exit(selftest())
    print("ใช้งาน: python vault.py selftest")
