#!/usr/bin/env python3
"""Reproduce the preview's public-data payload measurements. No model calls.

python -m venv /tmp/reific-evidence
/tmp/reific-evidence/bin/pip install -r scripts/evidence/requirements.txt
/tmp/reific-evidence/bin/python scripts/evidence/replay.py --check

The source files, exact counted payloads, and output are committed together.
This is deliberately a deterministic reference implementation, not a model eval.
"""

from __future__ import annotations

import argparse
import csv
import hashlib
import io
import json
import zipfile
from datetime import datetime
from pathlib import Path

import tiktoken

ROOT = Path(__file__).resolve().parents[2]
LOCAL = Path(__file__).resolve().parent
OUT = LOCAL if (LOCAL / "source/provenance.json").exists() else ROOT / "public/evidence"
ENCODING = "o200k_base"
ENC = tiktoken.get_encoding(ENCODING)


def sha(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def render_json(value: object) -> str:
    return json.dumps(value, indent=2, ensure_ascii=False) + "\n"


def csv_text(rows: list[dict], fields: list[str]) -> str:
    stream = io.StringIO(newline="")
    writer = csv.DictWriter(stream, fields, lineterminator="\n", extrasaction="ignore")
    writer.writeheader()
    writer.writerows(rows)
    return stream.getvalue()


def token_count(text: str) -> int:
    return len(ENC.encode(text, disallowed_special=()))


def delta(raw: int, evidence: int) -> float:
    return round(100 * (raw - evidence) / raw, 4)


def source_check(filename: str) -> bytes:
    source = OUT / "source" / filename
    payload = source.read_bytes()
    manifest = json.loads((OUT / "source/provenance.json").read_text())
    record = next(item for item in manifest if item["file"] == filename)
    assert sha(payload) == record["file_sha256"], f"Source checksum mismatch: {filename}"
    return payload


def build_energy() -> dict:
    data = source_check("energydata_complete.csv").decode("utf-8")
    original = list(csv.DictReader(io.StringIO(data)))
    rows = [
        dict(row, source_row=i + 2)
        for i, row in enumerate(original)
        if "2016-01-12 00:00:00" <= row["date"] < "2016-01-19 00:00:00"
    ]
    assert len(rows) == 1008
    times = [datetime.fromisoformat(row["date"]) for row in rows]
    assert all((b - a).total_seconds() == 600 for a, b in zip(times, times[1:]))
    values = [int(row["Appliances"]) for row in rows]
    candidates = [(sum(values[i:i + 3]), i) for i in range(len(values) - 2)]
    ranked = sorted(candidates, key=lambda candidate: (-candidate[0], candidate[1]))
    energy, index = ranked[0]
    event_rows = rows[index:index + 3]
    assert energy == max(sum(values[i:i + 3]) for i in range(len(values) - 2))
    question = "Which three consecutive 10-minute readings have the highest total appliance energy in the first seven complete days, and what can we conclude?"
    packet = {
        "schema": "reific.reference-evidence.v1",
        "status": "deterministic_public_data_replay",
        "question": question,
        "source": {
            "dataset": "UCI Appliances Energy Prediction",
            "doi": "10.24432/C5VC8G",
            "file": "energydata_complete.csv",
            "sha256": sha(data.encode()),
            "row_numbering": "1-based physical CSV lines; header is line 1",
            "timezone": "not supplied; retain source-local timestamps",
        },
        "coverage": {
            "start": rows[0]["date"], "end": rows[-1]["date"],
            "samples": len(rows), "cadence_seconds": 600,
            "missing_appliance_values": 0, "candidate_windows": len(candidates),
        },
        "finding": {
            "type": "maximum_rolling_three_reading_energy",
            "first_reading": event_rows[0]["date"],
            "last_reading": event_rows[-1]["date"],
            "reading_count": 3, "covered_energy_intervals_minutes": 30,
            "total_energy_Wh": energy,
            "equivalent_average_power_W": energy * 2,
            "source_rows": [row["source_row"] for row in event_rows],
            "observed_values_Wh": [int(row["Appliances"]) for row in event_rows],
            "ties_at_maximum": sum(total == energy for total, _ in candidates),
            "tie_break": "earliest first-reading timestamp",
        },
        "limits": [
            "This identifies a maximum in the selected week, not an abnormal or faulty state.",
            "Aggregate appliance energy does not identify which device ran or establish a cause.",
            "Source timestamps do not specify whether each 10-minute interval begins or ends at its label.",
            "A script with the same aggregation can produce the same finding; no model advantage is measured.",
        ],
        "next_evidence": "Device-level meters, schedules, and intervention records are required for a device or fault attribution.",
    }
    return {
        "slug": "building-demand", "question": question, "rows": rows,
        "fields": list(original[0].keys()), "projected_fields": ["date", "Appliances"],
        "packet": packet, "axis_unit": "Wh / 10-minute reading", "value_key": "Appliances",
        "event_start": index, "event_end": index + 2,
        "checks": ["Source bytes match recorded SHA-256", "All 1,008 readings have an exact 10-minute cadence", "All 1,006 candidate windows were compared", "Recorded source rows reproduce the maximum sum"],
    }


def build_occupancy() -> dict:
    data = source_check("datatraining.txt").decode("utf-8")
    reader = csv.reader(io.StringIO(data))
    fields = ["source_id"] + next(reader)
    full_rows = [dict(zip(fields, row), source_row=i + 2) for i, row in enumerate(reader)]
    # The target is deliberately unavailable to the observation-to-evidence path.
    rows = [{key: value for key, value in row.items() if key != "Occupancy"} for row in full_rows]
    assert len(rows) == 8143
    times = [datetime.fromisoformat(row["date"]) for row in rows]
    intervals = [(b - a).total_seconds() for a, b in zip(times, times[1:])]
    assert all(59 <= gap <= 61 for gap in intervals)
    values = [float(row["CO2"]) for row in rows]
    candidates = [(values[i + 30] - values[i], i) for i in range(len(rows) - 30)]
    rise, index = sorted(candidates, key=lambda candidate: (-candidate[0], candidate[1]))[0]
    event_rows = rows[index:index + 31]
    question = "Where is the largest CO2 rise across 30 consecutive sampling intervals, and do those sensor readings alone confirm room occupancy?"
    packet = {
        "schema": "reific.reference-evidence.v1",
        "status": "deterministic_public_data_replay",
        "question": question,
        "source": {
            "dataset": "UCI Occupancy Detection", "doi": "10.24432/C5X01N",
            "file": "datatraining.txt", "sha256": sha(data.encode()),
            "row_numbering": "1-based physical CSV lines; header is line 1",
            "timezone": "not supplied; retain source-local timestamps",
        },
        "coverage": {
            "start": rows[0]["date"], "end": rows[-1]["date"], "samples": len(rows),
            "cadence_seconds_min": min(intervals), "cadence_seconds_max": max(intervals),
            "missing_CO2_values": 0, "candidate_windows": len(candidates),
            "withheld_columns": ["Occupancy"],
        },
        "finding": {
            "type": "maximum_30_interval_CO2_rise", "start": event_rows[0]["date"],
            "end": event_rows[-1]["date"], "elapsed_seconds": (times[index + 30] - times[index]).total_seconds(),
            "start_CO2_ppm": values[index], "end_CO2_ppm": values[index + 30],
            "rise_ppm": round(rise, 6),
            "penultimate_CO2_ppm": values[index + 29],
            "following_CO2_ppm": values[index + 31] if index + 31 < len(values) else None,
            "source_rows": [row["source_row"] for row in event_rows],
            "observed_CO2_ppm": [float(row["CO2"]) for row in event_rows],
            "ties_at_maximum": sum(change == rise for change, _ in candidates),
            "occupancy_conclusion": "not established from these readings alone",
        },
        "limits": [
            "A rise in CO2 does not by itself prove occupancy, the number of people, or a ventilation fault.",
            "Labels were withheld before window selection and payload construction.",
            "Selection uses the observed series retrospectively; this is not an early-warning experiment.",
            "No classifier or language model was evaluated; predictive accuracy is unmeasured.",
        ],
        "next_evidence": "Review separately recorded occupancy observations and ventilation state before choosing an action.",
    }
    selected_labels = [int(row["Occupancy"]) for row in full_rows[index:index + 31]]
    assert sum(selected_labels) == 0
    label_audit = {
        "status": "retrospective_label_audit_not_in_agent_payload",
        "selected_source_rows": [row["source_row"] for row in event_rows],
        "occupancy_labels": selected_labels,
        "occupied_readings": sum(selected_labels), "total_readings": len(selected_labels),
        "meaning": "All 31 separately provided occupancy labels are zero: the source labels the selected window unoccupied despite the CO2 rise. This is a retrospective check of one selected window, not an occupancy prediction score or an online result.",
    }
    return {
        "slug": "room-occupancy", "question": question, "rows": rows,
        "fields": [field for field in fields if field not in ("Occupancy", "source_id")],
        "projected_fields": ["date", "CO2"], "packet": packet, "label_audit": label_audit,
        "axis_unit": "CO2 / ppm", "value_key": "CO2", "event_start": index, "event_end": index + 30,
        "checks": ["Source bytes match recorded SHA-256", "Every adjacent timestamp differs by 59–61 seconds", "All 8,113 candidate windows were compared", "Occupancy labels are absent from all counted payloads"],
    }


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--check", action="store_true", help="Recompute and verify saved artifacts without writing")
    args = parser.parse_args()
    artifacts: dict[str, str] = {}
    studies = []
    for task in (build_energy(), build_occupancy()):
        slug = task["slug"]
        raw = csv_text(task["rows"], task["fields"])
        projected = csv_text(task["rows"], task["projected_fields"])
        evidence = render_json(task["packet"])
        artifacts[f"{slug}/raw.csv"] = raw
        artifacts[f"{slug}/projected.csv"] = projected
        artifacts[f"{slug}/evidence.json"] = evidence
        if "label_audit" in task:
            artifacts[f"{slug}/label-audit.json"] = render_json(task["label_audit"])
        raw_count, projected_count, evidence_count = map(token_count, (raw, projected, evidence))
        question_prefix = task["question"] + "\n\n"
        step = max(1, len(task["rows"]) // 180)
        chart_indices = sorted(set(range(0, len(task["rows"]), step)) | set(range(max(0, task["event_start"] - 5), min(len(task["rows"]), task["event_end"] + 6))) | {len(task["rows"]) - 1})
        studies.append({
            "slug": slug, "sampleCount": len(task["rows"]), "fieldCount": len(task["fields"]),
            "question": task["question"], "packet": task["packet"], "checks": task["checks"],
            "tokens": {
                "raw": raw_count, "projected": projected_count, "evidence": evidence_count,
                "savedFromRaw": raw_count - evidence_count,
                "reductionFromRawPercent": delta(raw_count, evidence_count),
                "savedFromProjected": projected_count - evidence_count,
                "reductionFromProjectedPercent": delta(projected_count, evidence_count),
                "questionPlusRaw": token_count(question_prefix + raw),
                "questionPlusProjected": token_count(question_prefix + projected),
                "questionPlusEvidence": token_count(question_prefix + evidence),
            },
            "sha256": {"raw": sha(raw.encode()), "projected": sha(projected.encode()), "evidence": sha(evidence.encode())},
            "chart": {
                "unit": task["axis_unit"], "eventStart": task["event_start"], "eventEnd": task["event_end"],
                "points": [{"i": i, "t": task["rows"][i]["date"], "v": float(task["rows"][i][task["value_key"]])} for i in chart_indices],
            },
            "labelAudit": task.get("label_audit"),
        })
    raw_total = sum(study["tokens"]["raw"] for study in studies)
    projected_total = sum(study["tokens"]["projected"] for study in studies)
    evidence_total = sum(study["tokens"]["evidence"] for study in studies)
    report = {
        "version": "2026-09-15.1", "status": "reference_replay_payload_only",
        "encoding": ENCODING, "tokenizer": f"tiktoken=={tiktoken.__version__}",
        "studyCount": len(studies), "rawTokens": raw_total, "projectedTokens": projected_total,
        "evidenceTokens": evidence_total, "savedTokens": raw_total - evidence_total,
        "reductionPercent": delta(raw_total, evidence_total),
        "reductionFromProjectedPercent": delta(projected_total, evidence_total),
        "agentCalls": 0, "agentResolutionRate": None, "totalAgentTokenBurn": None,
        "scope": "Exact serialized data payload tokens only. No language model, paid API, task success, customer outcome, wall-clock advantage, or end-to-end cost was measured. Projection is a stronger simple baseline; the deterministic computations themselves can also run in SQL or Python.",
        "studies": studies,
    }
    artifacts["replay.json"] = render_json(report)
    artifacts["replay.py"] = Path(__file__).read_text()
    artifacts["requirements.txt"] = "tiktoken==0.11.0\n"
    for filename, content in artifacts.items():
        target = OUT / filename
        if args.check:
            assert target.read_bytes() == content.encode(), f"Artifact mismatch: {filename}"
        else:
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_bytes(content.encode())
    bundle_files = {name: text.encode() for name, text in artifacts.items()}
    for filename in ("README.md", "source/provenance.json", "source/energydata_complete.csv", "source/datatraining.txt"):
        bundle_files[filename] = (OUT / filename).read_bytes()
    bundle = io.BytesIO()
    with zipfile.ZipFile(bundle, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive:
        for filename, payload in sorted(bundle_files.items()):
            entry = zipfile.ZipInfo(filename, date_time=(2026, 9, 15, 0, 0, 0))
            entry.compress_type = zipfile.ZIP_DEFLATED
            entry.external_attr = 0o100644 << 16
            archive.writestr(entry, payload)
    bundle_target = OUT / "replay-bundle.zip"
    bundle_checked = bundle_target.exists()
    if args.check and bundle_checked:
        assert bundle_target.read_bytes() == bundle.getvalue(), "Reproduction bundle mismatch"
    elif not args.check:
        bundle_target.write_bytes(bundle.getvalue())
    print(render_json({"verified" if args.check else "written": len(artifacts) + int(bundle_checked or not args.check), "rawTokens": raw_total, "projectedTokens": projected_total, "evidenceTokens": evidence_total, "studies": [{"slug": item["slug"], "tokens": item["tokens"], "finding": item["packet"]["finding"]} for item in studies]}))


if __name__ == "__main__":
    main()
