#!/usr/bin/env python3
"""Reproduce the article's synthetic merge example in a new, private Git repo."""

import datetime
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import tempfile


def main():
    root = Path(__file__).resolve().parent
    runs = root / "runs"
    runs.mkdir(exist_ok=True)
    run = Path(tempfile.mkdtemp(prefix="merge-example-", dir=runs))
    repo = run / "repo"
    repo.mkdir()
    logs = run / "logs"
    logs.mkdir()
    env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
    env.pop("NODE_OPTIONS", None)
    env.update({"GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": os.devnull,
                "GIT_CONFIG_SYSTEM": os.devnull, "GIT_TERMINAL_PROMPT": "0",
                "GIT_MERGE_AUTOEDIT": "no", "TZ": "UTC"})
    git = shutil.which("git")
    node = shutil.which("node")
    if not git or not node:
        raise RuntimeError("Git and Node are required; no packages are installed.")
    commands = []

    def command(args, expected=0, log=None):
        p = subprocess.run(args, cwd=repo, env=env, text=True,
                           stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                           timeout=30)
        commands.append({"argv": args, "exit_code": p.returncode,
                         "output": p.stdout})
        (run / "commands.json").write_text(json.dumps(commands, indent=2) + "\n")
        if log:
            (logs / log).write_text(p.stdout)
        if p.returncode != expected:
            raise RuntimeError(f"Unexpected exit {p.returncode}: {args}\n{p.stdout}")
        return p.stdout

    def g(*args, **kwargs):
        return command([git, *args], **kwargs)

    def write(name, value):
        path = repo / name
        path.parent.mkdir(exist_ok=True, parents=True)
        path.write_text(value)

    def commit(message):
        g("add", ".")
        g("commit", "-m", message)
        return g("rev-parse", "HEAD").strip()

    def test(label, expected):
        total, passed, failed = expected
        out = command([node, "--test", "--test-reporter=tap"],
                      expected=1 if failed else 0, log=f"{label}.tap")
        observed = {}
        for key in ("tests", "pass", "fail"):
            match = re.search(rf"^# {key} (\d+)$", out, re.MULTILINE)
            if not match:
                raise RuntimeError(f"Missing {key} total in {label}")
            observed[key] = int(match.group(1))
        if tuple(observed[k] for k in ("tests", "pass", "fail")) != expected:
            raise RuntimeError(f"Unexpected result in {label}: {observed}")
        return {"command": "node --test --test-reporter=tap",
                "tests": total, "passed": passed, "failed": failed,
                "exit_code": 1 if failed else 0,
                "commit": g("rev-parse", "HEAD").strip(),
                "log": f"logs/{label}.tap"}

    versions = {"git": g("--version").strip(),
                "node": command([node, "--version"]).strip()}
    g("init", "--initial-branch=main", "--template=")
    g("config", "--local", "user.name", "araNET Editorial Fixture")
    g("config", "--local", "user.email", "fixture@example.invalid")
    g("config", "--local", "commit.gpgSign", "false")
    g("config", "--local", "core.hooksPath", str(run / "no-hooks"))
    (run / "no-hooks").mkdir()
    write("user.mjs", "export function getUser() {\n  return { name: 'Taylor' };\n}\n")
    write("greeting.mjs", "import { getUser } from './user.mjs';\nexport function greeting() {\n  return `Hello ${getUser().name}`;\n}\n")
    write("test/greeting.test.mjs", "import test from 'node:test';\nimport assert from 'node:assert/strict';\nimport { greeting } from '../greeting.mjs';\ntest('greeting includes the name', () => {\n  assert.equal(greeting(), 'Hello Taylor');\n});\n")
    baseline = commit("Create synthetic user and greeting")
    results = {"baseline": test("baseline", (1, 1, 0))}

    g("switch", "-c", "rename-display-name")
    write("user.mjs", "export function getUser() {\n  return { displayName: 'Taylor' };\n}\n")
    write("greeting.mjs", "import { getUser } from './user.mjs';\nexport function greeting() {\n  return `Hello ${getUser().displayName}`;\n}\n")
    write("test/user.test.mjs", "import test from 'node:test';\nimport assert from 'node:assert/strict';\nimport { getUser } from '../user.mjs';\ntest('user exposes displayName', () => {\n  assert.equal(getUser().displayName, 'Taylor');\n});\n")
    commit("Rename field and update existing greeting")
    results["rename_branch"] = test("rename-branch", (2, 2, 0))

    g("switch", "-c", "add-receipt", baseline)
    write("receipt.mjs", "import { getUser } from './user.mjs';\nexport function receipt() {\n  return `For ${getUser().name}`;\n}\n")
    write("test/receipt.test.mjs", "import test from 'node:test';\nimport assert from 'node:assert/strict';\nimport { receipt } from '../receipt.mjs';\ntest('receipt includes the name', () => {\n  assert.equal(receipt(), 'For Taylor');\n});\n")
    commit("Add receipt against the original field")
    results["receipt_branch"] = test("receipt-branch", (2, 2, 0))

    g("switch", "rename-display-name")
    merge_output = g("merge", "--no-ff", "--no-edit", "add-receipt", log="merge.txt")
    unmerged = g("ls-files", "--unmerged")
    if unmerged.strip():
        raise RuntimeError("Expected no unresolved merge entries")
    results["merged"] = test("merged", (3, 2, 1))
    actual = command([node, "--input-type=module", "-e",
                      "import { receipt } from './receipt.mjs'; console.log(receipt());"]).strip()
    if actual != "For undefined":
        raise RuntimeError(f"Unexpected merged receipt: {actual}")
    merged_commit = g("rev-parse", "HEAD").strip()
    write("receipt.mjs", "import { getUser } from './user.mjs';\nexport function receipt() {\n  return `For ${getUser().displayName}`;\n}\n")
    fixed_commit = commit("Update receipt to use displayName")
    results["repaired"] = test("repaired", (3, 3, 0))
    if g("status", "--porcelain").strip() or g("remote", "-v").strip():
        raise RuntimeError("Fixture must be clean and have no remotes")
    hashes = {p.relative_to(repo).as_posix(): hashlib.sha256(p.read_bytes()).hexdigest()
              for p in sorted(repo.rglob("*.mjs"))}
    receipt = {"status": "verified", "kind": "synthetic-integration-example",
               "checked_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
               "versions": versions, "results": results,
               "merge": {"exit_code": 0, "unmerged_index_entries": 0,
                         "commit": merged_commit, "receipt_actual": actual,
                         "receipt_expected": "For Taylor", "log": "logs/merge.txt"},
               "fixed_commit": fixed_commit, "final_files_sha256": hashes,
               "limits": ["Synthetic code authored for this article; not a customer application.",
                          "No coding agents, Pierre libraries, Codex or Pando were tested.",
                          "No dependency installs, external remotes, hosted services or paid API calls.",
                          "The merged test exit 1 is an observed, deliberately constructed failure."]}
    (run / "verification.json").write_text(json.dumps(receipt, indent=2) + "\n")
    print(json.dumps({"run_directory": str(run), "versions": versions,
                      "results": {k: {s: v[s] for s in ("tests", "passed", "failed", "exit_code")}
                                  for k, v in results.items()},
                      "merge_exit_code": 0, "unmerged_index_entries": 0}, indent=2))


if __name__ == "__main__":
    main()
