#!/bin/sh
set -eu

: "${PRE15_DUT:?PRE15_DUT is required}"
: "${PRE15_ROOT:?PRE15_ROOT is required}"
: "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}"
: "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}"
. "$PRE15_LIB_DIR/runner.sh"

gate=$PRE15_DUT/tests/pre15/gates/P15-005.sh
tuple_oracle=$PRE15_DUT/tests/pre15/fixtures/B06-tuples.json
ownership_oracle=$PRE15_DUT/tests/pre15/fixtures/B06-ownership.json
artifacts=$PRE15_RUN_DIR/artifacts

for tool in cc python3 sha256sum; do
	command -v "$tool" >/dev/null 2>&1 || \
		pre15_infra_blocked "missing B06 host tool: $tool"
done

pre15_record_fixture b06-gate "$gate"
pre15_record_fixture b06-tuple-oracle "$tuple_oracle"
pre15_record_fixture b06-ownership-oracle "$ownership_oracle"
for source in internal.h data.c decompressor.c inode.c super.c xattr.c zdata.c zmap.c; do
	pre15_record_fixture "b06-$source" "$PRE15_DUT/src/$source"
done

mkdir -p "$artifacts"
pre15_target_reached

if "$gate" --worktree --oracle "$tuple_oracle" \
	--output "$artifacts/gate" >"$artifacts/gate.stdout" \
	2>"$artifacts/gate.stderr"; then
	:
else
	pre15_dut_fail 'B06 G02 candidate replay failed'
fi

if python3 - "$PRE15_ROOT" "$ownership_oracle" "$artifacts/gate" \
	"$artifacts/ownership-check.json" <<'PY'
from __future__ import annotations

import hashlib
import json
from pathlib import Path
import re
import sys


root = Path(sys.argv[1])
expected = json.loads(Path(sys.argv[2]).read_text(encoding="ascii"))
gate_dir = Path(sys.argv[3])
output = Path(sys.argv[4])
result = json.loads((gate_dir / "result.json").read_text(encoding="ascii"))
ownership = json.loads((gate_dir / "ownership.json").read_text(encoding="ascii"))

if result["status"] != "GO" or result["tuple_status"] != "PASS":
    raise SystemExit(f"candidate tuple result is not GO: {result!r}")
if not result["object_mode"] or result["oracle_equal"] is not True:
    raise SystemExit(f"candidate did not replay the frozen object oracle: {result!r}")
if ownership["status"] != "PASS" or ownership["failures"]:
    raise SystemExit(f"candidate ownership result failed: {ownership['failures']!r}")
if ownership["consumers"] != expected["consumers"]:
    raise SystemExit("ownership consumer set changed")
if ownership["direct_metadata_functions"] != expected["direct_metadata_functions"]:
    raise SystemExit("direct metadata consumer inventory changed")
if ownership["helper_paths"]["paths"] != expected["helper_paths"]:
    raise SystemExit("actual helper lifecycle paths changed")

metric_order = expected["metric_order"]
actual_metrics = {
    item["function"]: [item[field] for field in metric_order]
    for item in ownership["candidate_contract"]["function_metrics"]
}
if actual_metrics != expected["function_metrics"]:
    raise SystemExit("per-function ownership metrics changed")


def extract_function(source: str, name: str) -> str:
    match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE)
    if not match:
        raise SystemExit(f"backend function missing: {name}")
    start = source.rfind("\n\n", 0, match.start()) + 2
    brace = source.find("{", match.end())
    depth = 0
    state = "code"
    index = brace
    while index < len(source):
        char = source[index]
        following = source[index + 1] if index + 1 < len(source) else ""
        if state == "code":
            if char == "/" and following == "*":
                state = "block"
                index += 2
                continue
            if char == "/" and following == "/":
                state = "line"
                index += 2
                continue
            if char == '"':
                state = "string"
            elif char == "'":
                state = "character"
            elif char == "{":
                depth += 1
            elif char == "}":
                depth -= 1
                if depth == 0:
                    return source[start : index + 1] + "\n"
        elif state == "block" and char == "*" and following == "/":
            state = "code"
            index += 2
            continue
        elif state == "line" and char == "\n":
            state = "code"
        elif state in {"string", "character"}:
            if char == "\\":
                index += 2
                continue
            if (state == "string" and char == '"') or (
                state == "character" and char == "'"
            ):
                state = "code"
        index += 1
    raise SystemExit(f"unterminated backend function: {name}")


backend_hashes = {}
for key, frozen_hash in expected["freebsd_backend_function_sha256"].items():
    filename, function = key.split(":", 1)
    source = (root / "repo-pre-15" / "src" / filename).read_text(encoding="utf-8")
    current_hash = hashlib.sha256(
        extract_function(source, function).encode("utf-8")
    ).hexdigest()
    backend_hashes[key] = current_hash
    if current_hash != frozen_hash:
        raise SystemExit(f"FreeBSD backend lifecycle changed: {key}")

internal_path = root / "repo-pre-15" / "src" / "internal.h"
internal = internal_path.read_text(encoding="utf-8")
match = re.search(
    r"^struct erofs_map_blocks \{\n.*?^\};\n",
    internal,
    re.MULTILINE | re.DOTALL,
)
if not match:
    raise SystemExit("missing legacy map tuple object")
map_hash = hashlib.sha256(match.group(0).encode("utf-8")).hexdigest()
if map_hash != expected["map_blocks_sha256"]:
    raise SystemExit("B07 map objectization leaked into B06")

required = [
    "struct erofs_buf {",
    "#define EROFS_BUF_INITIALIZER { .data = NULL, .release = NULL }",
    "void erofs_put_metabuf(struct erofs_buf *buf);",
    "int erofs_bread(struct erofs_sb_info *sbi, erofs_off_t off, size_t len, void **bufp);",
    "void erofs_brelse(void *buf);",
]
for marker in required:
    if marker not in internal:
        raise SystemExit(f"required B06/raw API marker missing: {marker}")
if re.search(
    r"erofs_read_metadata\s*\([^;]*void\s*\*\*bufp\s*\)\s*;",
    internal,
    re.MULTILINE | re.DOTALL,
):
    raise SystemExit("legacy metadata void-pointer API remains")
for filename in ("dir.c", "namei.c"):
    text = (root / "repo-pre-15" / "src" / filename).read_text(encoding="utf-8")
    if "struct erofs_buf" in text or "erofs_put_metabuf" in text:
        raise SystemExit(f"raw directory consumer was objectized: {filename}")

summary = {
    "status": "PASS",
    "tuple_cases": result["case_count"],
    "tuple_oracle_equal": result["oracle_equal"],
    "ownership_paths": result["ownership_path_count"],
    "helper_paths": len(expected["helper_paths"]),
    "function_metrics": len(actual_metrics),
    "freebsd_backend_functions": len(backend_hashes),
    "map_blocks_sha256": map_hash,
    "qemu": result["qemu"],
    "full_feature_suite": result["full_feature_suite"],
}
with output.open("x", encoding="ascii") as stream:
    json.dump(summary, stream, ensure_ascii=True, indent=2, sort_keys=True)
    stream.write("\n")
print("B06 ownership PASS tuples=14 paths=22 helper=5 functions=28")
PY
then
	:
else
	pre15_dut_fail 'B06 ownership fixture comparison failed'
fi

sha256sum "$artifacts/gate/result.json" "$artifacts/gate/tuples.json" \
	"$artifacts/gate/ownership.json" "$artifacts/ownership-check.json" \
	>"$artifacts/SHA256SUMS"
printf 'B06 PASS explicit metadata ownership and frozen tuple replay\n'
