#!/bin/sh
set -eu

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

fixture_dir=$PRE15_DUT/tests/pre15/fixtures
policy=$fixture_dir/B12-readahead-policy.json
kld_builder=$fixture_dir/B12-build-kld.sh
artifacts=$PRE15_RUN_DIR/artifacts

for tool in awk clang file git nm python3 sha256sum timeout; do
	command -v "$tool" >/dev/null 2>&1 || \
		pre15_infra_blocked "missing B12 host tool: $tool"
done

pre15_record_fixture b12-policy "$policy"
pre15_record_fixture b12-kld-builder "$kld_builder"
pre15_record_fixture b12-case "$PRE15_DUT/tests/pre15/cases/B12-readahead.sh"
for source in data.c dir.c internal.h; do
	pre15_record_fixture "b12-$source" "$PRE15_DUT/src/$source"
done
mkdir -p "$artifacts"
pre15_target_reached

if python3 -B - "$PRE15_ROOT" "$PRE15_DUT" "$policy" \
    "$artifacts/B12-policy-report.json" <<'PY'
from __future__ import annotations

import csv
import hashlib
import json
from pathlib import Path
import re
import statistics
import subprocess
import sys


root = Path(sys.argv[1])
dut = Path(sys.argv[2])
policy_path = Path(sys.argv[3])
report_path = Path(sys.argv[4])
policy = json.loads(policy_path.read_text(encoding="ascii"))
if policy.get("schema") != 1 or policy.get("candidate") != "P15-087":
    raise SystemExit("invalid B12 policy identity")


def digest(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


source_hashes = {
    relative: digest(dut / relative)
    for relative in policy["source_sha256"]
}
if source_hashes != policy["source_sha256"]:
    raise SystemExit("B12 production source differs from the accepted source set")

changed = subprocess.run(
    [
        "git", "-C", str(root), "diff", "--name-only",
        policy["baseline"], "--", "repo-pre-15/src",
    ],
    check=True,
    text=True,
    stdout=subprocess.PIPE,
).stdout.splitlines()
expected_changed = [
    "repo-pre-15/src/data.c",
    "repo-pre-15/src/dir.c",
    "repo-pre-15/src/internal.h",
]
if sorted(changed) != expected_changed:
    raise SystemExit(f"B12 production write set mismatch: {changed}")

data = (dut / "src/data.c").read_text(encoding="utf-8")
directory = (dut / "src/dir.c").read_text(encoding="utf-8")
internal = (dut / "src/internal.h").read_text(encoding="utf-8")
if "cluster_read(" in data + directory + internal:
    raise SystemExit("B12 must not introduce cluster_read")
if data.count("breadn(") != 1:
    raise SystemExit("B12 must contain one backing-vnode breadn call")
if not re.search(r"breadn\(dif->devvp,", data):
    raise SystemExit("B12 breadn is not issued on erofs_device_info.devvp")
required_data = (
    "#define EROFS_DIR_READAHEAD_BYTES\t(1024 * 1024)",
    "#define EROFS_DIR_READAHEAD_SLOTS\t(EROFS_DIR_READAHEAD_BYTES / PAGE_SIZE)",
    "future.m_dif != current.m_dif",
    "future.m_pa != current.m_pa + step",
    "vi->datalayout == EROFS_INODE_FLAT_PLAIN",
    "return (erofs_read_data_impl(sbi, vi, loff, len, 0, bufp));",
)
for marker in required_data:
    if marker not in data:
        raise SystemExit(f"missing B12 data policy marker: {marker}")
if directory.count("erofs_read_data_readahead(") != 1:
    raise SystemExit("B12 directory hint is not limited to readdir")
if "sequential = logical_off == 0;" not in directory:
    raise SystemExit("B12 random seek no-op predicate is absent")
if internal.count("erofs_read_data_readahead(") != 1:
    raise SystemExit("B12 internal prototype is absent or duplicated")

evidence = root / policy["gate_evidence"]
result_path = evidence / "result.json"
rows_path = evidence / "runs.tsv"
if digest(result_path) != policy["gate_result_sha256"]:
    raise SystemExit("B12 gate result hash mismatch")
if digest(rows_path) != policy["gate_rows_sha256"]:
    raise SystemExit("B12 gate rows hash mismatch")
result = json.loads(result_path.read_text(encoding="ascii"))
if result.get("status") != "GO" or result.get("b12") != "AUTHORIZED":
    raise SystemExit("P15-087 did not authorize B12")
if result.get("qemu") != "PASS" or not all(result.get("checks", {}).values()):
    raise SystemExit("P15-087 runtime or semantic checks are incomplete")
thresholds = result.get("thresholds", {})
if thresholds != {
    "maximum_extra_provider_reads_percent": policy["maximum_extra_provider_reads_percent"],
    "minimum_cold_median_improvement_percent": policy["minimum_cold_median_improvement_percent"],
}:
    raise SystemExit("P15-087 thresholds differ from the frozen B12 policy")

rows = []
with rows_path.open(encoding="ascii", newline="") as source:
    for raw in csv.reader(source, delimiter="\t"):
        if len(raw) != 10:
            raise SystemExit(f"invalid B12 gate row: {raw}")
        rows.append({
            "variant": raw[0],
            "run": raw[1],
            "mode": raw[3],
            "elapsed_ns": int(raw[4]),
            "provider_reads": int(raw[5]),
            "entry_count": int(raw[7]),
            "hash": raw[8],
            "final_cookie": int(raw[9]),
        })
sequential = [row for row in rows if row["mode"] == "sequential"]
random_rows = [row for row in rows if row["mode"] == "random"]
baseline = [row for row in sequential if row["variant"] == "baseline"]
candidate = [row for row in sequential if row["variant"] == "candidate"]
if len(baseline) != 5 or len(candidate) != 5 or len(random_rows) != 2:
    raise SystemExit("B12 gate does not contain the complete 5+5 and 1+1 sample set")
baseline_ns = statistics.median(row["elapsed_ns"] for row in baseline)
candidate_ns = statistics.median(row["elapsed_ns"] for row in candidate)
improvement = (baseline_ns - candidate_ns) * 100.0 / baseline_ns
baseline_reads = statistics.median(row["provider_reads"] for row in baseline)
candidate_reads = statistics.median(row["provider_reads"] for row in candidate)
extra_reads = (candidate_reads - baseline_reads) * 100.0 / baseline_reads
if improvement < policy["minimum_cold_median_improvement_percent"]:
    raise SystemExit("B12 recomputed cold median improvement misses the gate")
if extra_reads > policy["maximum_extra_provider_reads_percent"]:
    raise SystemExit("B12 recomputed provider-read delta misses the gate")
if any(
    (row["entry_count"], row["hash"], row["final_cookie"])
    != (18002, "427bb414efa99dc0", 3880368)
    for row in sequential
):
    raise SystemExit("B12 sequential correctness rows differ from the oracle")
if {
    (row["provider_reads"], row["entry_count"], row["hash"], row["final_cookie"])
    for row in random_rows
} != {(19, 292, "9805eadece9f500e", 1998932)}:
    raise SystemExit("B12 random path is not an exact no-op pair")

report = {
    "baseline_samples": len(baseline),
    "candidate_samples": len(candidate),
    "baseline_median_elapsed_ns": baseline_ns,
    "candidate_median_elapsed_ns": candidate_ns,
    "cold_median_improvement_percent": improvement,
    "extra_provider_reads_percent": extra_reads,
    "gate": "GO",
    "qemu_runtime": "PASS",
    "random_samples": len(random_rows),
    "source_sha256": source_hashes,
    "status": "PASS",
    "window_bytes": policy["maximum_readahead_bytes"],
}
report_path.write_text(
    json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii"
)
PY
then
	:
else
	pre15_dut_fail 'B12 source, write-set, or frozen G11 policy replay failed'
fi
pre15_record_fixture b12-policy-report "$artifacts/B12-policy-report.json"

module=$PRE15_CASE_TMP/B12-erofs-zstdio0.ko
if ! timeout -k 10 600 /bin/sh "$kld_builder" "$PRE15_DUT" \
    "$PRE15_FREEBSD_SRC" "$module" "$PRE15_CASE_TMP/kld-work" \
    >"$artifacts/B12-kld-build.stdout" 2>"$artifacts/B12-kld-build.stderr"; then
	pre15_dut_fail 'B12 exact-ABI zstdio0 KLD build failed'
fi
pre15_record_module "$module"
file "$module" >"$artifacts/B12-kld-file.txt"
sha256sum "$module" >"$artifacts/B12-kld-sha256.txt"
nm -g "$module" | LC_ALL=C sort >"$artifacts/B12-kld-nm-global.txt"
nm -u "$module" | LC_ALL=C sort >"$artifacts/B12-kld-nm-u.txt"
if ! awk '$NF == "erofs_read_data_readahead" { found = 1 } END { exit !found }' \
    "$artifacts/B12-kld-nm-global.txt"; then
	pre15_dut_fail 'B12 exact-ABI KLD lacks the directory readahead symbol'
fi
if grep -q ' ZSTD_' "$artifacts/B12-kld-nm-u.txt"; then
	pre15_dut_fail 'B12 zstdio0 KLD contains an unexpected Zstd symbol'
fi

printf '%s\n' \
	'B12-policy PASS: exact source/write set and frozen 5+5 G11 statistics' \
	'K zstdio0 PASS: FreeBSD 15 exact-ABI cross-KLD and symbol contract' \
	'TC183 correctness/performance PASS from immutable P15-087 QEMU evidence'
