#!/usr/bin/env python3
"""Convert Bangcle auth tables from JS base64 format to binary package data.

Reads ``bangcle_auth_tables.js`` from the project root and writes
``src/pybyd/data/bangcle_tables.bin``.

Usage:
    python scripts/convert_bangcle_tables.py [path/to/bangcle_auth_tables.js]
"""

from __future__ import annotations

import base64
import json
import re
import struct
import sys
from pathlib import Path

# Table order and expected sizes (must match _bangcle_block.BangcleTables).
TABLE_SPECS: list[tuple[str, str, int]] = [
    ("inv_round", "invRound", 0x28000),
    ("inv_xor", "invXor", 0x3C000),
    ("inv_first", "invFirst", 0x1000),
    ("round", "round", 0x28000),
    ("xor", "xor", 0x3C000),
    ("final", "final", 0x1000),
    ("perm_decrypt", "permDecrypt", 8),
    ("perm_encrypt", "permEncrypt", 8),
]

MAGIC = b"BGTB"
VERSION = 1
HEADER_SIZE = 4 + 2 + 2  # magic + version + count
INDEX_ENTRY_SIZE = 4 + 4  # offset + length


def extract_tables_from_js(js_path: Path) -> dict[str, str]:
    """Extract base64 table strings from the JS module."""
    text = js_path.read_text(encoding="utf-8")

    # The JS file has: module.exports = Object.freeze({ "key": "base64...", ... });
    # Extract the JSON object inside Object.freeze(...)
    match = re.search(r"Object\.freeze\((\{[\s\S]*?\})\)", text)
    if not match:
        raise ValueError(f"Could not find Object.freeze({{...}}) in {js_path}")

    json_str = match.group(1)
    # The JS object uses double-quoted keys, so it's valid JSON.
    return json.loads(json_str)  # type: ignore[no-any-return]


def build_binary(tables_b64: dict[str, str]) -> bytes:
    """Build the binary table file from base64 table strings."""
    table_count = len(TABLE_SPECS)
    index_size = table_count * INDEX_ENTRY_SIZE
    data_offset_base = HEADER_SIZE + index_size

    decoded_tables: list[bytes] = []
    for _py_name, js_name, expected_size in TABLE_SPECS:
        b64 = tables_b64.get(js_name)
        if b64 is None:
            raise ValueError(f"Missing table '{js_name}' in JS source")
        raw = base64.b64decode(b64)
        if len(raw) != expected_size:
            raise ValueError(f"Table '{js_name}': expected {expected_size} bytes, got {len(raw)}")
        decoded_tables.append(raw)

    # Build header
    header = bytearray()
    header.extend(MAGIC)
    header.extend(struct.pack("<H", VERSION))
    header.extend(struct.pack("<H", table_count))

    # Build index
    current_offset = data_offset_base
    for table_data in decoded_tables:
        header.extend(struct.pack("<II", current_offset, len(table_data)))
        current_offset += len(table_data)

    # Concatenate data
    result = bytes(header)
    for table_data in decoded_tables:
        result += table_data

    return result


def main() -> None:
    """Entry point."""
    # Determine JS source path
    if len(sys.argv) > 1:
        js_path = Path(sys.argv[1])
    else:
        # Default: project root relative to this script
        script_dir = Path(__file__).resolve().parent
        project_root = script_dir.parent.parent
        js_path = project_root / "bangcle_auth_tables.js"

    if not js_path.exists():
        print(f"Error: {js_path} not found", file=sys.stderr)
        sys.exit(1)

    print(f"Reading tables from: {js_path}")
    tables_b64 = extract_tables_from_js(js_path)
    print(f"Found {len(tables_b64)} tables: {', '.join(tables_b64.keys())}")

    binary = build_binary(tables_b64)
    print(f"Binary size: {len(binary)} bytes ({len(binary) / 1024:.1f} KB)")

    # Write output
    script_dir = Path(__file__).resolve().parent
    output_path = script_dir.parent / "src" / "pybyd" / "data" / "bangcle_tables.bin"
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_bytes(binary)
    print(f"Written to: {output_path}")


if __name__ == "__main__":
    main()
