#!/usr/bin/env python3
"""
p1_unpack.py — Извлечение потоков из контейнера 1С v8 (.hbk)
"""

import json
import os
import struct
import sys

BLOCK_HDR = 31  # размер блочного адреса
NO_NEXT = 0x7FFFFFFF  # нет следующей страницы


# ─── Чтение блочного адреса ──────────────────────────────────────────


def parse_block_hdr(data, offset):
    """
    Разбирает блочный адрес: \\r\\nDOCSIZE PAGESIZE NEXTPAGE \\r\\n
    Возвращает (doc_size, page_size, next_page) или None.
    """
    if offset < 0 or offset + BLOCK_HDR > len(data):
        return None
    if (
        data[offset : offset + 2] != b"\r\n"
        or data[offset + 29 : offset + 31] != b"\r\n"
    ):
        return None
    try:
        txt = data[offset + 2 : offset + 29].decode("ascii").strip().split()
        if len(txt) != 3:
            return None
        return int(txt[0], 16), int(txt[1], 16), int(txt[2], 16)
    except Exception:
        return None


def read_block(data, offset):
    """
    Читает ПОЛНОЕ содержимое блока (может быть цепочка страниц).
    Возвращает bytes.
    """
    first = parse_block_hdr(data, offset)
    if first is None:
        return None

    doc_size = first[0]  # реальный размер данных
    buf = bytearray()
    cur = offset
    visited = set()

    while cur != NO_NEXT and cur not in visited:
        visited.add(cur)
        hdr = parse_block_hdr(data, cur)
        if hdr is None:
            break
        _, page_size, next_page = hdr
        start = cur + BLOCK_HDR
        buf.extend(data[start : start + page_size])
        cur = next_page

    return bytes(buf[:doc_size])


# ─── Разбор метаданных потока ────────────────────────────────────────


def parse_meta(raw):
    """
    Метаданные потока:
      creation  FILETIME (8 байт)
      modified  FILETIME (8 байт)
      attrs     uint32   (4 байта)
      name      UTF-16LE с нулевым терминатором
    """
    if not raw or len(raw) < 20:
        return None, raw
    name = raw[20:].decode("utf-16-le").rstrip("\x00")
    return name, raw


# ─── Основная функция ────────────────────────────────────────────────


def extract(filepath, out_dir="hbk_streams"):
    with open(filepath, "rb") as f:
        data = f.read()

    magic, page_sz, version, _ = struct.unpack_from("<IIII", data, 0)
    print(f"Файл     : {filepath}  ({len(data):,} байт)")
    print(f"Magic    : 0x{magic:08X}")
    print(f"PageSize : 0x{page_sz:X}")
    print(f"Version  : 0x{version:X}")

    # ─── TOC ─────────────────────────────────────────────────────────
    toc_raw = read_block(data, 16)  # TOC идёт сразу после 16-байтного заголовка
    if toc_raw is None:
        sys.exit("ОШИБКА: не удалось прочитать TOC")

    n_entries = len(toc_raw) // 12
    print(f"TOC      : {len(toc_raw)} байт  →  {n_entries} записей\n")

    os.makedirs(out_dir, exist_ok=True)
    manifest = []

    for i in range(n_entries):
        h_addr, d_addr, _ = struct.unpack_from("<III", toc_raw, i * 12)
        if h_addr == 0 and d_addr == 0:
            break

        # метаданные
        meta_raw = read_block(data, h_addr)
        name, _ = parse_meta(meta_raw)
        if not name:
            name = f"stream_{i}"

        # данные
        content = read_block(data, d_addr)
        c_len = len(content) if content else 0

        print(f"  [{i}] {name}")
        print(f"       header @ 0x{h_addr:X}   data @ 0x{d_addr:X}")
        print(f"       content : {c_len:,} байт")
        if content:
            print(f"       head hex: {content[:32].hex(' ')}")

        # ─── сохраняем ──────────────────────────────────────────────
        safe = name.replace("/", "_").replace("\\", "_")

        with open(os.path.join(out_dir, f"{safe}.meta"), "wb") as f:
            f.write(meta_raw)

        if content:
            with open(os.path.join(out_dir, safe), "wb") as f:
                f.write(content)

        manifest.append(
            {
                "index": i,
                "name": name,
                "header_addr": h_addr,
                "data_addr": d_addr,
                "content_size": c_len,
                "meta_size": len(meta_raw) if meta_raw else 0,
            }
        )

    with open(os.path.join(out_dir, "_manifest.json"), "w", encoding="utf-8") as f:
        json.dump(manifest, f, indent=2, ensure_ascii=False)

    print(f"\n{'=' * 50}")
    print(f"Извлечено потоков: {len(manifest)}")
    print(f"Каталог: {out_dir}/")
    return manifest


if __name__ == "__main__":
    fp = sys.argv[1] if len(sys.argv) > 1 else "shcntx_ru.hbk"
    extract(fp)
