#!/usr/bin/env python3
"""Execute synthetic deletion scenarios and write independently observed results.
Usage: python3 run.py [output.json]
Python standard library only. Databases exist only in a temporary directory.
"""
import hashlib
import json
from pathlib import Path
import platform
import sqlite3
import sys
import tempfile
from datetime import datetime, timezone

from deletion import Request, Store, buggy_delete

ROOT = Path(__file__).resolve().parent
POLICY = json.loads((ROOT / "policy.json").read_text())
TARGET = POLICY["target_subject"]
CONTROL = POLICY["control_subject"]


def create_stores(directory, prefix):
    stores = []
    for name in POLICY["destinations"]:
        conn = sqlite3.connect(directory / (prefix + "-" + name + ".sqlite"))
        conn.execute("CREATE TABLE records (subject TEXT PRIMARY KEY, value TEXT)")
        conn.executemany("INSERT INTO records VALUES (?, ?)",
                         [(TARGET, "synthetic target"), (CONTROL, "synthetic control")])
        conn.commit()
        stores.append(Store(name, conn))
    return stores


def observe(stores):
    # Independent oracle reads database rows directly, not Request.state or contains().
    return {
        store.name: {
            "target_rows": store.connection.execute(
                "SELECT COUNT(*) FROM records WHERE subject = ?", (TARGET,)
            ).fetchone()[0],
            "control_rows": store.connection.execute(
                "SELECT COUNT(*) FROM records WHERE subject = ?", (CONTROL,)
            ).fetchone()[0],
        }
        for store in stores
    }


def all_absent(rows):
    return bool(rows) and all(row["target_rows"] == 0 for row in rows.values())


def controls_preserved(rows):
    return all(row["control_rows"] == 1 for row in rows.values())


def run():
    checks, scenarios = [], []

    def check(name, condition):
        checks.append({"name": name, "passed": bool(condition)})

    with tempfile.TemporaryDirectory(prefix="proof-privacy-teaching-") as temporary:
        directory = Path(temporary)
        defective = create_stores(directory, "buggy")
        fixed = create_stores(directory, "fixed")
        backup = sqlite3.connect(directory / "analytics-backup.sqlite")
        try:
            defective[1].fail_next_delete = True
            claimed = buggy_delete(defective, TARGET)
            rows = observe(defective)
            detected = claimed == "complete" and not all_absent(rows)
            scenarios.append({"id": "buggy_partial_failure", "reported_state": claimed,
                              "observed": rows, "false_completion_detected": detected})
            check("oracle_detects_false_completion", detected)
            check("buggy_primary_deleted_secondary_remains",
                  rows["profiles"]["target_rows"] == 0 and rows["analytics"]["target_rows"] == 1)

            fixed[1].connection.backup(backup)
            request = Request(TARGET, fixed)
            fixed[1].fail_next_delete = True
            result = request.attempt()
            rows = observe(fixed)
            scenarios.append({"id": "fixed_partial_failure", **result, "observed": rows})
            check("failure_is_named_and_unresolved", result["state"] == "unresolved"
                  and result["destinations"]["analytics"] == "unavailable"
                  and rows["analytics"]["target_rows"] == 1)

            result = request.attempt()
            rows = observe(fixed)
            scenarios.append({"id": "retry_after_recovery", **result, "observed": rows})
            check("retry_deletes_both", result["state"] == "complete" and all_absent(rows))
            check("retry_preserves_other_subject", controls_preserved(rows))

            result = request.attempt()
            rows = observe(fixed)
            scenarios.append({"id": "repeat_completed_request", **result, "observed": rows})
            check("repeat_is_idempotent", result["state"] == "complete"
                  and all_absent(rows) and controls_preserved(rows))

            backup.backup(fixed[1].connection)
            result = request.reconcile()
            rows = observe(fixed)
            scenarios.append({"id": "reconcile_after_restore", **result, "observed": rows})
            check("restore_reopens_request", result["state"] == "unresolved"
                  and rows["analytics"]["target_rows"] == 1)

            result = request.attempt()
            rows = observe(fixed)
            scenarios.append({"id": "delete_restored_row", **result, "observed": rows})
            check("restored_live_row_deleted", result["state"] == "complete"
                  and all_absent(rows) and controls_preserved(rows))
            backup_rows = backup.execute(
                "SELECT COUNT(*) FROM records WHERE subject = ?", (TARGET,)
            ).fetchone()[0]
            check("backup_is_explicitly_outside_completion_scope", backup_rows == 1)
        finally:
            for store in defective + fixed:
                store.connection.close()
            backup.close()
    return {
        "schema": "proof.teaching.privacy-obligation.v1",
        "generated_at_utc": datetime.now(timezone.utc).isoformat(),
        "runtime": {"python": platform.python_version(), "sqlite": sqlite3.sqlite_version},
        "policy": POLICY["id"], "policy_revision": POLICY["revision"],
        "scope": "Synthetic rows in two local SQLite live stores; no production or legal assessment",
        "source_sha256": {name: hashlib.sha256((ROOT / name).read_bytes()).hexdigest()
                          for name in ["deletion.py", "run.py", "policy.json"]},
        "scenarios": scenarios, "checks": checks,
        "checks_passed": sum(c["passed"] for c in checks), "checks_total": len(checks),
        "backup_target_rows_before_temp_cleanup": backup_rows,
        "all_checks_passed": all(c["passed"] for c in checks),
    }


if __name__ == "__main__":
    output = Path(sys.argv[1]) if len(sys.argv) > 1 else ROOT / "observed.json"
    result = run()
    output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n")
    print(json.dumps({"output": str(output), "checks_passed": result["checks_passed"],
                      "checks_total": result["checks_total"],
                      "all_checks_passed": result["all_checks_passed"]}, sort_keys=True))
    sys.exit(0 if result["all_checks_passed"] else 1)
