#!/usr/bin/env python3
"""
SignChain CLI — Blockchain Document Authentication Tool
https://signchain.id

Cara install:
  pip install requests
  python signchain-cli.py config --api-key YOUR_API_KEY

Cara pakai:
  python signchain-cli.py hash dokumen.pdf
  python signchain-cli.py register dokumen.pdf
  python signchain-cli.py register dokumen.pdf --ref "INV-001" --desc "Invoice Q1 2026"
  python signchain-cli.py verify <sha256_hash_atau_file>
  python signchain-cli.py batch ./folder_dokumen/

Theme:
  python signchain-cli.py config --theme dark    (terminal gelap — warna terang)
  python signchain-cli.py config --theme light   (terminal terang — warna gelap)
"""

import os
import sys
import time
import hashlib
import argparse
import configparser
from pathlib import Path

try:
    import requests
except ImportError:
    print("ERROR: Library 'requests' belum terinstall.")
    print("Jalankan: pip install requests")
    sys.exit(1)

# ── Konfigurasi ───────────────────────────────────────────────────────────────
CONFIG_DIR  = Path.home() / ".signchain"
CONFIG_FILE = CONFIG_DIR / "config.ini"
API_BASE    = "https://signchain.id/api"
VERSION     = "1.2.0"

SUPPORTED_EXTENSIONS = {
    '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
    '.jpg', '.jpeg', '.png', '.gif', '.webp',
    '.txt', '.csv', '.json', '.xml',
    '.zip', '.tar', '.gz',
    '.mp4', '.mp3', '.avi',
}

# Windows ANSI support
if sys.platform == 'win32':
    os.system('color')

# ── Theme System ──────────────────────────────────────────────────────────────
ANSI_RESET = '\033[0m'
ANSI_BOLD  = '\033[1m'
ANSI_DIM   = '\033[2m'

# Theme DARK  = warna terang → untuk terminal berlatar GELAP (default)
# Theme LIGHT = warna gelap  → untuk terminal berlatar TERANG/PUTIH
THEMES = {
    # dark  = terminal berlatar PUTIH/TERANG → semua teks gelap agar terbaca
    'dark': {
        'name':    'Dark (terminal putih)',
        'PRIMARY': '\033[36m',   # Cyan gelap
        'SUCCESS': '\033[32m',   # Hijau gelap
        'ERROR':   '\033[31m',   # Merah gelap
        'WARN':    '\033[33m',   # Kuning gelap
        'INFO':    '\033[36m',   # Cyan gelap
        'TEXT':    '\033[30m',   # Hitam
        'MUTED':   '\033[90m',   # Abu gelap
        'LABEL':   '\033[90m',   # Abu gelap
        'HASH':    '\033[36m',   # Cyan gelap
        'BOLD':    ANSI_BOLD,
        'RESET':   ANSI_RESET,
    },
    # light = terminal berlatar GELAP/HITAM → semua teks terang agar terbaca
    'light': {
        'name':    'Light (terminal gelap)',
        'PRIMARY': '\033[96m',   # Cyan terang
        'SUCCESS': '\033[92m',   # Hijau terang
        'ERROR':   '\033[91m',   # Merah terang
        'WARN':    '\033[93m',   # Kuning terang
        'INFO':    '\033[96m',   # Cyan terang
        'TEXT':    '\033[97m',   # Putih
        'MUTED':   '\033[37m',   # Abu terang
        'LABEL':   '\033[37m',   # Abu terang
        'HASH':    '\033[96m',   # Cyan terang
        'BOLD':    ANSI_BOLD,
        'RESET':   ANSI_RESET,
    }
}

# T = aktif theme, diisi saat load_config()
T = THEMES['light']

def set_theme(name: str):
    global T
    T = THEMES.get(name, THEMES['light'])

# ── Config helper ─────────────────────────────────────────────────────────────
def load_config() -> dict:
    cfg = {'api_key': '', 'api_base': API_BASE, 'theme': 'light'}
    if CONFIG_FILE.exists():
        parser = configparser.ConfigParser()
        parser.read(CONFIG_FILE)
        if 'signchain' in parser:
            cfg['api_key']  = parser['signchain'].get('api_key', '')
            cfg['api_base'] = parser['signchain'].get('api_base', API_BASE)
            cfg['theme']    = parser['signchain'].get('theme', 'light')
    set_theme(cfg['theme'])
    return cfg

def save_config(api_key: str = None, api_base: str = None, theme: str = None):
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    # Baca yang sudah ada dulu
    cfg = load_config()
    if api_key  is not None: cfg['api_key']  = api_key
    if api_base is not None: cfg['api_base'] = api_base
    if theme    is not None: cfg['theme']    = theme
    parser = configparser.ConfigParser()
    parser['signchain'] = {
        'api_key':  cfg['api_key'],
        'api_base': cfg['api_base'],
        'theme':    cfg['theme'],
    }
    with open(CONFIG_FILE, 'w') as f:
        parser.write(f)
    CONFIG_FILE.chmod(0o600)
    set_theme(cfg['theme'])

def mask_key(key: str) -> str:
    if len(key) < 12: return '•' * len(key)
    return key[:8] + '•' * (len(key) - 12) + key[-4:]

# ── Print helpers ─────────────────────────────────────────────────────────────
def ok(msg):          print(f"  {T['SUCCESS']}✓{T['RESET']} {T['TEXT']}{msg}{T['RESET']}")
def err(msg):         print(f"  {T['ERROR']}✗{T['RESET']} {T['ERROR']}{msg}{T['RESET']}")
def warn(msg):        print(f"  {T['WARN']}⚠{T['RESET']} {T['WARN']}{msg}{T['RESET']}")
def info(msg):        print(f"  {T['INFO']}ℹ{T['RESET']} {T['MUTED']}{msg}{T['RESET']}")
def muted(msg):       print(f"  {T['MUTED']}{msg}{T['RESET']}")
def label(k, v):      print(f"  {T['LABEL']}{k:<12}{T['RESET']}: {T['TEXT']}{v}{T['RESET']}")
def label_col(k, v):  print(f"  {T['LABEL']}{k:<12}{T['RESET']}: {T['PRIMARY']}{v}{T['RESET']}")

def banner():
    p = T['PRIMARY']; b = T['BOLD']; r = T['RESET']; m = T['MUTED']; tx = T['TEXT']
    line1 = '⬡  SignChain.id  —  Blockchain Auth CLI'
    line2 = 'Daftarkan & verifikasi dokumen on-chain'
    # Samakan lebar dengan box hasil verify/register (min_w=72 + 4 padding)
    W     = 76
    pad1  = W - len(line1) - 2
    pad2  = W - len(line2) - 2
    print(f'')
    print(f'  {p}{b}╔{chr(9552)*W}╗{r}')
    print(f'  {p}{b}║{r}  {tx}{line1}{p}{" "*pad1}{b}║{r}')
    print(f'  {p}{b}║{r}  {m}{line2}{p}{" "*pad2}{b}║{r}')
    print(f'  {p}{b}╚{chr(9552)*W}╝{r}')
    print(f'  {m}v{VERSION}  •  https://signchain.id  •  theme: {T["name"]}{r}')
    print()

def section(title):
    print(f"\n  {T['PRIMARY']}◆{T['RESET']} {T['BOLD']}{T['TEXT']}{title}{T['RESET']}")
    print(f"  {T['MUTED']}{'─'*48}{T['RESET']}")

def spinner_msg(msg):
    frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏']
    for i in range(14):
        print(f"\r  {T['PRIMARY']}{frames[i%len(frames)]}{T['RESET']} {T['MUTED']}{msg}{T['RESET']}", end='', flush=True)
        time.sleep(0.07)
    print(f"\r  {T['PRIMARY']}◆{T['RESET']} {T['MUTED']}{msg}{T['RESET']}          ")

def progress_bar(current, total, width=28):
    pct    = current / total if total else 0
    filled = int(width * pct)
    bar    = f"{T['PRIMARY']}{'█'*filled}{T['MUTED']}{'░'*(width-filled)}{T['RESET']}"
    return f"[{bar}] {T['TEXT']}{current}/{total}{T['RESET']} {T['MUTED']}({pct*100:.0f}%){T['RESET']}"

def _strip_ansi(text: str) -> str:
    """Hapus semua ANSI escape code untuk menghitung panjang teks asli."""
    import re
    return re.sub(r'\033\[[0-9;]*m', '', text)

def box(title, lines, color=None):
    """Box dengan lebar otomatis menyesuaikan konten terpanjang."""
    if color is None:
        color = T['PRIMARY']
    r = T['RESET']; b = T['BOLD']

    # Hitung lebar berdasarkan konten
    min_w   = 72
    title_w = len(title) + 4
    lines_w = max((len(_strip_ansi(l)) for l in lines), default=0) + 4
    W       = max(min_w, title_w, lines_w)

    # Top border
    print(f"\n  {color}╔{'═'*W}╗{r}")

    # Title centered
    pad_l = (W - len(title)) // 2
    pad_r = W - len(title) - pad_l
    print(f"  {color}║{r}{' '*pad_l}{b}{color}{title}{r}{' '*pad_r}{color}║{r}")
    print(f"  {color}╠{'═'*W}╣{r}")

    # Lines
    for line in lines:
        clean = _strip_ansi(line)
        padding = max(0, W - 2 - len(clean))
        print(f"  {color}║{r}  {line}{' '*padding}{color}║{r}")

    # Bottom border
    print(f"  {color}╚{'═'*W}╝{r}\n")

# ── Hash helper ───────────────────────────────────────────────────────────────
def sha256_file(filepath: Path) -> str:
    h          = hashlib.sha256()
    size       = filepath.stat().st_size
    processed  = 0
    chunk_size = 65536
    with open(filepath, 'rb') as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk: break
            h.update(chunk)
            processed += len(chunk)
            if size > 512 * 1024:
                pct = processed / size
                bar = int(28 * pct)
                print(f"\r  [{T['PRIMARY']}{'█'*bar}{'░'*(28-bar)}{T['RESET']}] {pct*100:.0f}%", end='', flush=True)
    if size > 512 * 1024:
        print(f"\r  [{T['PRIMARY']}{'█'*28}{T['RESET']}] {T['SUCCESS']}100% ✓{T['RESET']}          ")
    return h.hexdigest()

def file_size_str(b: int) -> str:
    if b < 1024:     return f"{b} B"
    if b < 1024**2:  return f"{b/1024:.1f} KB"
    if b < 1024**3:  return f"{b/1024**2:.1f} MB"
    return f"{b/1024**3:.1f} GB"

# ── Perintah: config ──────────────────────────────────────────────────────────
def cmd_config(args):
    load_config()
    banner()
    section("Konfigurasi")
    print()

    changed = False

    if args.api_key:
        save_config(api_key=args.api_key)
        ok(f"API Key disimpan ke {T['PRIMARY']}{CONFIG_FILE}{T['RESET']}")
        changed = True

    if args.theme:
        if args.theme not in ('dark', 'light'):
            err("Theme tidak valid. Pilih: dark atau light")
            sys.exit(1)
        save_config(theme=args.theme)
        set_theme(args.theme)
        ok(f"Theme diubah ke: {T['PRIMARY']}{T['name']}{T['RESET']}")
        changed = True

    if args.show:
        cfg = load_config()
        print()
        label("API Key",  mask_key(cfg['api_key']) if cfg['api_key'] else f"{T['WARN']}(belum diset){T['RESET']}")
        label("Endpoint", cfg['api_base'])
        label("Theme",    f"{T['PRIMARY']}{cfg['theme']}{T['RESET']} — {T['MUTED']}{T['name']}{T['RESET']}")
        print()
        # Preview theme
        print(f"  {T['MUTED']}Preview theme saat ini:{T['RESET']}")
        ok("Sukses / Valid")
        err("Error / Gagal")
        warn("Peringatan")
        info("Informasi")
        label_col("Hash", "a3f5d9c2e1b4a7f8...")
        print()
        return

    if changed:
        ok("Konfigurasi tersimpan!")
        print(f"\n  {T['MUTED']}Coba:{T['RESET']} {T['PRIMARY']}python signchain-cli.py status{T['RESET']}\n")
    elif not args.show:
        warn("Gunakan --api-key, --theme, atau --show")
        print()
        print(f"  {T['MUTED']}Contoh:{T['RESET']}")
        muted("  python signchain-cli.py config --api-key YOUR_KEY")
        muted("  python signchain-cli.py config --theme dark")
        muted("  python signchain-cli.py config --theme light")
        muted("  python signchain-cli.py config --show")
        print()

# ── Perintah: status ──────────────────────────────────────────────────────────
def cmd_status(args):
    cfg = load_config()
    banner()
    if not cfg['api_key']:
        err("API Key belum dikonfigurasi.")
        info("python signchain-cli.py config --api-key YOUR_KEY")
        return

    section("Status Koneksi")
    print()
    spinner_msg("Menghubungi SignChain.id...")

    try:
        res = requests.get(f"{cfg['api_base']}/auth/session.php", timeout=10)
        if res.status_code in [200, 401]:
            ok(f"Server {T['PRIMARY']}signchain.id{T['RESET']} {T['SUCCESS']}online ✓{T['RESET']}")
        else:
            warn(f"Server merespons HTTP {res.status_code}")
        print()
        label("API Key",  mask_key(cfg['api_key']))
        label("Endpoint", cfg['api_base'])
        label("Theme",    f"{cfg['theme']} — {T['name']}")
        label("Status",   f"{T['SUCCESS']}Terhubung{T['RESET']}")
    except requests.exceptions.ConnectionError:
        err("Tidak dapat terhubung. Cek koneksi internet.")
    except Exception as e:
        err(f"Error: {e}")
    print()

# ── Perintah: hash ────────────────────────────────────────────────────────────
def cmd_hash(args):
    load_config()
    banner()
    filepath = Path(args.file)
    if not filepath.exists():
        err(f"File tidak ditemukan: {filepath}"); sys.exit(1)

    section("Hitung SHA-256")
    print()
    label("File",   filepath.name)
    label("Ukuran", file_size_str(filepath.stat().st_size))
    label("Tipe",   filepath.suffix.upper().lstrip('.') or 'unknown')
    print()

    file_hash = sha256_file(filepath)

    # Hash box
    p = T['PRIMARY']; r = T['RESET']; b = T['BOLD']; tx = T['TEXT']
    print(f"\n  {p}┌{'─'*66}┐{r}")
    print(f"  {p}│{r}  {b}{tx}SHA-256 Hash{r}" + ' '*54 + f"{p}│{r}")
    print(f"  {p}│{r}  {T['HASH']}{file_hash}{r}  {p}│{r}")
    print(f"  {p}└{'─'*66}┘{r}\n")

    if args.copy:
        try:
            import subprocess
            if sys.platform == 'darwin':
                subprocess.run(['pbcopy'], input=file_hash.encode(), check=True)
            elif sys.platform == 'linux':
                subprocess.run(['xclip', '-selection', 'clipboard'], input=file_hash.encode(), check=True)
            elif sys.platform == 'win32':
                subprocess.run('clip', input=file_hash.encode(), shell=True, check=True)
            ok("Hash disalin ke clipboard!")
        except Exception:
            pass
    print()

# ── Perintah: register ────────────────────────────────────────────────────────
def cmd_register(args):
    cfg = load_config()
    banner()
    if not cfg['api_key']:
        err("API Key belum dikonfigurasi.")
        info("python signchain-cli.py config --api-key YOUR_KEY")
        sys.exit(1)

    filepath = Path(args.file)
    if not filepath.exists():
        err(f"File tidak ditemukan: {filepath}"); sys.exit(1)

    ext = filepath.suffix.lower()
    section("Register Dokumen ke Blockchain")
    print()
    label("File",      filepath.name)
    label("Ukuran",    file_size_str(filepath.stat().st_size))
    label("Ref ID",    args.ref  or f"{T['MUTED']}(tidak ada){T['RESET']}")
    label("Deskripsi", args.desc or f"{T['MUTED']}(tidak ada){T['RESET']}")
    print()

    spinner_msg("Menghitung SHA-256...")
    file_hash = sha256_file(filepath)
    label_col("Hash", file_hash)
    print()

    if args.dry_run:
        warn("[DRY RUN] Tidak ada yang dikirim ke blockchain.")
        ok("Simulasi selesai.")
        return

    if not args.yes:
        w = T['WARN']; r = T['RESET']; p = T['PRIMARY']; tx = T['TEXT']
        print(f"  {w}┌─ Konfirmasi Register ────────────────────────────┐{r}")
        print(f"  {w}│{r}  Daftarkan ke {p}SUI Mainnet{r}? Tindakan {tx}permanen{r}.    {w}│{r}")
        print(f"  {w}└──────────────────────────────────────────────────┘{r}")
        s = T['SUCCESS']; e = T['ERROR']; b = T['BOLD']
        confirm = input(f"\n  {b}Lanjutkan? [{s}y{r}{b}/{e}N{r}{b}]: {r}").strip().lower()
        if confirm != 'y':
            warn("Dibatalkan."); return

    print()
    spinner_msg("Mengirim ke SUI Blockchain...")

    try:
        res = requests.post(
            f"{cfg['api_base']}/register.php",
            json={'file_hash': file_hash, 'file_name': filepath.name,
                  'file_type': ext.lstrip('.') or 'unknown',
                  'ref_id': args.ref or None, 'description': args.desc or None},
            headers={'X-API-Key': cfg['api_key'], 'Content-Type': 'application/json'},
            timeout=60
        )
        data = res.json()

        if data.get('status') == 'success':
            d  = data.get('data', {})
            p  = T['PRIMARY']; m = T['MUTED']; r = T['RESET']; s = T['SUCCESS']
            box(f"✓  REGISTER BERHASIL", [
                f"{s}Dokumen berhasil didaftarkan ke SUI Mainnet!{r}",
                "",
                f"{p}Hash      {r}: {file_hash}",
                f"{p}Object ID {r}: {d.get('object_id','—')}",
                f"{p}TX Digest {r}: {d.get('tx_digest','—')}",
                f"{p}Network   {r}: SUI Mainnet",
                f"{p}Quota     {r}: {m}{d.get('quota_remaining','—')} tersisa{r}",
            ], T['SUCCESS'])
            if d.get('tx_digest'):
                print(f"  {T['MUTED']}Explorer:{T['RESET']} {T['PRIMARY']}https://suivision.xyz/txblock/{d['tx_digest']}{T['RESET']}\n")
        else:
            msg = data.get('message', 'Gagal mendaftarkan dokumen.')
            box("✗  GAGAL", [msg], T['ERROR'])
            if res.status_code == 401:   info("API Key tidak valid atau KYC belum disetujui.")
            elif res.status_code == 409:
                info("Hash ini sudah terdaftar sebelumnya.")
                d = data.get('data', {})
                if d.get('object_id'): label_col("Object ID", d['object_id'])
            elif res.status_code == 429: info("Quota habis. Hubungi tim SignChain.id")
            sys.exit(1)

    except requests.exceptions.ConnectionError:
        err("Tidak dapat terhubung ke SignChain.id"); sys.exit(1)
    except requests.exceptions.Timeout:
        err("Timeout — coba lagi beberapa saat."); sys.exit(1)
    except Exception as e:
        err(f"Error: {e}"); sys.exit(1)

# ── Perintah: verify ──────────────────────────────────────────────────────────
def cmd_verify(args):
    cfg    = load_config()
    banner()
    target = args.target

    section("Verifikasi Dokumen")
    print()

    if os.path.exists(target):
        label("File",   Path(target).name)
        label("Ukuran", file_size_str(Path(target).stat().st_size))
        print()
        spinner_msg("Menghitung SHA-256...")
        file_hash = sha256_file(Path(target))
        label_col("Hash", file_hash)
    elif len(target) == 64 and all(c in '0123456789abcdefABCDEF' for c in target):
        file_hash = target.lower()
        label_col("Hash", file_hash)
    else:
        err("Input harus berupa path file atau SHA-256 hash (64 hex).")
        sys.exit(1)

    print()
    spinner_msg("Memverifikasi di SUI Blockchain...")

    try:
        res  = requests.post(f"{cfg['api_base']}/verify.php",
                             json={'file_hash': file_hash}, timeout=15)
        data = res.json()
        p = T['PRIMARY']; m = T['MUTED']; r = T['RESET']
        s = T['SUCCESS']; e = T['ERROR']; w = T['WARN']

        if data.get('status') == 'success':
            d      = data.get('data', {})
            status = d.get('result', d.get('status', 'UNKNOWN'))
            pub    = d.get('publisher', {})
            pname  = pub.get('full_name', pub.get('name', '—'))
            org    = pub.get('organization', '')

            if status == 'VALID':
                lines = [
                    f"{s}✓  DOKUMEN VALID — TERDAFTAR DI BLOCKCHAIN{r}",
                    "",
                    f"{p}Hash      {r}: {file_hash}",
                    f"{p}Object ID {r}: {d.get('object_id','—')}",
                    f"{p}TX Digest {r}: {d.get('tx_digest','—')}",
                    f"{p}Terdaftar {r}: {d.get('registered_at','—')}",
                    f"{p}Ref ID    {r}: {d.get('reference_id','—')}",
                    f"{p}Publisher {r}: {pname}",
                ]
                if org: lines.append(f"{p}Organisasi{r}: {org}")
                lines += ["", f"{p}Network   {r}: SUI Mainnet"]
                box("VALID", lines, T['SUCCESS'])
                if d.get('object_id'):
                    print(f"  {m}Explorer:{r} {p}https://suivision.xyz/object/{d['object_id']}{r}\n")

            elif status == 'REVOKED':
                box("REVOKED", [
                    f"{e}✗  DOKUMEN TELAH DICABUT (REVOKED){r}",
                    "",
                    f"{p}Hash      {r}: {file_hash}",
                    f"{p}Object ID {r}: {d.get('object_id','—')}",
                    f"{p}Dicabut   {r}: {d.get('revoked_at','—')}",
                ], T['ERROR'])
            else:
                warn("Status tidak dikenali dari server.")
        else:
            box("TIDAK DITEMUKAN", [
                f"{w}⚠  Hash belum terdaftar di blockchain,{r}",
                f"   atau file telah dimodifikasi.",
                "",
                f"{p}Hash      {r}: {file_hash}",
            ], T['WARN'])

    except requests.exceptions.ConnectionError:
        err("Tidak dapat terhubung ke SignChain.id"); sys.exit(1)
    except Exception as e:
        err(f"Error: {e}"); sys.exit(1)

# ── Perintah: batch ───────────────────────────────────────────────────────────
def cmd_batch(args):
    cfg = load_config()
    banner()
    if not cfg['api_key']:
        err("API Key belum dikonfigurasi."); sys.exit(1)

    folder = Path(args.folder)
    if not folder.is_dir():
        err(f"Bukan folder: {folder}"); sys.exit(1)

    files = [f for f in sorted(folder.iterdir())
             if f.is_file() and f.suffix.lower() in SUPPORTED_EXTENSIONS]
    if not files:
        warn(f"Tidak ada file yang didukung di {folder}"); return

    section(f"Batch Register — {len(files)} file ditemukan")
    print()
    for i, f in enumerate(files, 1):
        print(f"  {T['MUTED']}{i:2d}.{T['RESET']} {T['TEXT']}{f.name}{T['RESET']} {T['MUTED']}({file_size_str(f.stat().st_size)}){T['RESET']}")
    print()

    s = T['SUCCESS']; e = T['ERROR']; b = T['BOLD']; r = T['RESET']
    p = T['PRIMARY']
    confirm = input(f"  {b}Daftarkan {p}{len(files)}{r}{b} file ke blockchain? [{s}y{r}{b}/{e}N{r}{b}]: {r}").strip().lower()
    if confirm != 'y':
        warn("Dibatalkan."); return

    print()
    success_count = 0
    fail_count    = 0

    for i, filepath in enumerate(files, 1):
        print(f"  {T['PRIMARY']}[{i}/{len(files)}]{T['RESET']} {T['TEXT']}{filepath.name}{T['RESET']}")
        print(f"        {progress_bar(i-1, len(files))}")
        file_hash = sha256_file(filepath)
        muted(f"        {file_hash}…")
        try:
            res  = requests.post(
                f"{cfg['api_base']}/register.php",
                json={'file_hash': file_hash, 'file_name': filepath.name,
                      'file_type': filepath.suffix.lstrip('.') or 'unknown'},
                headers={'X-API-Key': cfg['api_key'], 'Content-Type': 'application/json'},
                timeout=60
            )
            data = res.json()
            if data.get('status') == 'success':
                ok("Berhasil didaftarkan"); success_count += 1
            else:
                err(data.get('message', 'Gagal.')); fail_count += 1
        except Exception as ex:
            err(f"Error: {ex}"); fail_count += 1
        print()

    print(f"  {T['PRIMARY']}{'─'*48}{T['RESET']}")
    print(f"  {T['BOLD']}Hasil Akhir:{T['RESET']}  {progress_bar(success_count, len(files))}")
    print()
    ok(f"{success_count} file berhasil didaftarkan")
    if fail_count: err(f"{fail_count} file gagal")
    print()

# ── Main ──────────────────────────────────────────────────────────────────────
def main():
    # Load config awal agar theme aktif sebelum banner
    load_config()

    parser = argparse.ArgumentParser(
        prog='signchain-cli',
        description='SignChain CLI — Blockchain Document Authentication',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Contoh:
  python signchain-cli.py config --api-key YOUR_API_KEY
  python signchain-cli.py config --theme dark
  python signchain-cli.py config --theme light
  python signchain-cli.py config --show
  python signchain-cli.py hash laporan.pdf
  python signchain-cli.py register kontrak.pdf --ref "KONTRAK-001"
  python signchain-cli.py verify laporan.pdf
  python signchain-cli.py batch ./dokumen/
        """
    )
    parser.add_argument('--version', action='version', version=f'SignChain CLI v{VERSION}')
    sub = parser.add_subparsers(dest='command', title='Perintah')

    p_cfg = sub.add_parser('config', help='Konfigurasi API Key dan theme')
    p_cfg.add_argument('--api-key', metavar='KEY',           help='Set API Key')
    p_cfg.add_argument('--theme',   choices=['dark','light'], help='Set theme: dark atau light')
    p_cfg.add_argument('--show',    action='store_true',      help='Tampilkan konfigurasi saat ini')

    sub.add_parser('status', help='Cek koneksi dan konfigurasi')

    p_hash = sub.add_parser('hash', help='Hitung SHA-256 hash file')
    p_hash.add_argument('file', help='Path file')
    p_hash.add_argument('--copy', action='store_true', help='Salin hash ke clipboard')

    p_reg = sub.add_parser('register', help='Daftarkan dokumen ke blockchain')
    p_reg.add_argument('file', help='Path file')
    p_reg.add_argument('--ref',     metavar='REF_ID', help='Reference ID')
    p_reg.add_argument('--desc',    metavar='TEXT',   help='Deskripsi dokumen')
    p_reg.add_argument('--yes',     action='store_true', help='Skip konfirmasi')
    p_reg.add_argument('--dry-run', action='store_true', help='Simulasi tanpa kirim')

    p_ver = sub.add_parser('verify', help='Verifikasi dokumen di blockchain')
    p_ver.add_argument('target', help='Path file atau SHA-256 hash')

    p_batch = sub.add_parser('batch', help='Register semua file dalam folder')
    p_batch.add_argument('folder', help='Path folder')

    args = parser.parse_args()
    if not args.command:
        banner(); parser.print_help(); return

    {'config': cmd_config, 'status': cmd_status, 'hash': cmd_hash,
     'register': cmd_register, 'verify': cmd_verify, 'batch': cmd_batch
     }[args.command](args)

if __name__ == '__main__':
    main()
