#!/usr/bin/env python3
"""Independent B21 superblock ordering oracle."""

from __future__ import annotations

import argparse
import json
from pathlib import Path
import struct


SUPER = 1024
PAGE_SHIFT = 12
FEATURE_COMPAT_SB_CHKSUM = 0x00000001
FEATURE_INCOMPAT_ALL = 0x000001FF
FEATURE_INCOMPAT_48BIT = 0x00000080
FEATURE_INCOMPAT_XATTR_PREFIXES = 0x00000040
CRC32C_POLY = 0x82F63B78


def crc32c(data: bytes | bytearray, seed: int = 0xFFFFFFFF) -> int:
    value = seed
    for byte in data:
        value ^= byte
        for _ in range(8):
            value = (value >> 1) ^ (CRC32C_POLY if value & 1 else 0)
    return value & 0xFFFFFFFF


def u16(data: bytes, offset: int) -> int:
    return struct.unpack_from("<H", data, offset)[0]


def u32(data: bytes, offset: int) -> int:
    return struct.unpack_from("<I", data, offset)[0]


def u64(data: bytes, offset: int) -> int:
    return struct.unpack_from("<Q", data, offset)[0]


def checksum_valid(data: bytes) -> bool:
    block_bits = data[SUPER + 12]
    if not 9 <= block_bits <= PAGE_SHIFT:
        return False
    span = 1 << block_bits
    if span > SUPER:
        span -= SUPER
    end = SUPER + span
    expected = u32(data, SUPER + 4)
    return expected == crc32c(data[SUPER + 8 : end], 0x5045B54A)


def classify(data: bytes) -> tuple[int, str]:
    if u32(data, SUPER) != 0xE0F5E1E2:
        return 22, "magic"
    block_bits = data[SUPER + 12]
    if not 9 <= block_bits <= PAGE_SHIFT:
        return 22, "block-size"
    feature_compat = u32(data, SUPER + 8)
    if feature_compat & FEATURE_COMPAT_SB_CHKSUM and not checksum_valid(data):
        return 97, "checksum"
    if data[SUPER + 90] != 0:
        return 45, "dirblkbits"
    feature_incompat = u32(data, SUPER + 80)
    if feature_incompat & ~FEATURE_INCOMPAT_ALL:
        if u32(data, SUPER + 92) == 0xFFFFFFFF:
            return 45, "feature-before-prefix"
        return 45, "feature-incompat"
    if 128 + data[SUPER + 13] * 16 > (1 << PAGE_SHIFT) - SUPER:
        return 22, "super-extension"
    blocks = u32(data, SUPER + 36)
    root_nid_8b = u64(data, SUPER + 112)
    if feature_incompat & FEATURE_INCOMPAT_48BIT and root_nid_8b != 0:
        blocks |= u16(data, SUPER + 14) << 32
    if blocks == 0:
        return 97, "blocks-zero"
    if blocks << block_bits > len(data):
        return 6, "provider-size"
    if (
        feature_incompat & FEATURE_INCOMPAT_XATTR_PREFIXES
        and data[SUPER + 91] != 0
        and u32(data, SUPER + 92) == 0xFFFFFFFF
    ):
        return 97, "prefix-offset"
    return 0, "accepted"


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--fixtures", type=Path, required=True)
    parser.add_argument("--report", type=Path, required=True)
    args = parser.parse_args()
    index = json.loads(
        (args.fixtures / "fixture-index.json").read_text(encoding="ascii")
    )
    results = []
    for item in index["cases"]:
        path = args.fixtures / item["filename"]
        actual_errno, actual_reject = classify(path.read_bytes())
        passed = (
            actual_errno == item["expected_errno"]
            and actual_reject == item["expected_reject"]
        )
        results.append(
            {
                "actual_errno": actual_errno,
                "actual_reject": actual_reject,
                "expected_errno": item["expected_errno"],
                "expected_reject": item["expected_reject"],
                "id": item["id"],
                "passed": passed,
            }
        )
    report = {
        "batch": "B21",
        "case_count": len(results),
        "passed_count": sum(item["passed"] for item in results),
        "results": results,
        "schema": 1,
        "status": "PASS" if all(item["passed"] for item in results) else "FAIL",
    }
    args.report.write_text(
        json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii"
    )
    for item in results:
        print(
            f"{'PASS' if item['passed'] else 'FAIL'} {item['id']} "
            f"errno={item['actual_errno']} reject={item['actual_reject']}"
        )
    return 0 if report["status"] == "PASS" else 1


if __name__ == "__main__":
    raise SystemExit(main())
