"""The runner's work-branch git choreography, run for real.

The GitHub Actions runner is JavaScript embedded in a Python string, so the only honest way to
test pushCheckpoint / promoteToPreview is to extract the runner's own functions into a node
harness and drive them against a real bare remote in temp directories. Covers: a checkpoint
carries the (normally untracked) manifests and only ever fast-forwards; promotion to preview is a
fast-forward when preview is unmoved, a clean merge when it moved compatibly, and a reported
`preview_diverged` (with preview and the working tree untouched) on a conflict. Never a force
push."""

from __future__ import annotations

import json
import os
import shutil
import subprocess
import tempfile
from uuid import uuid4

import pytest
from src.domain.codegen.actions_pipeline import build_codegen_actions_runner

NODE = shutil.which("node")
GIT = shutil.which("git")
pytestmark = pytest.mark.skipif(not (NODE and GIT), reason="node and git are required")

RUN_ID = str(uuid4())
BRANCH = f"p2hs/codegen/{RUN_ID}"


def _extract() -> str:
    script = build_codegen_actions_runner()
    exec_start = script.index("const exec = (cmd, args, options = {}) => new Promise((resolve) => {")
    exec_end = script.index("\n});\n", exec_start) + len("\n});\n")
    helpers_start = script.index("// ---- Run-scoped work branch")
    helpers_end = script.index("async function callback(payload, body) {")
    # the runner's own safe-staging rules (allowlist + leak scan), used by the failure recovery
    staging_start = script.index("function p2hsCommitMessage(message) {")
    staging_end = script.index("function isInternalCodegenPath(value) {")
    # the Core-owned manifest projection, used on every attempt
    compose_start = script.index("// feature-packs.json is a Core-owned control-plane manifest")
    verify_start = script.index("async function verifyCoreComposition(payload) {")
    compose_end = script.index("\n}\n", verify_start) + len("\n}\n")
    prelude = (
        'import { spawn } from "node:child_process";\n'
        'import { existsSync } from "node:fs";\n'
        'import { mkdir, readFile, writeFile } from "node:fs/promises";\n'
        "let activePayload = null;\n"
        "function postProgress() {}\n"
        "function log(message) { console.log(`LOG ${message}`); }\n"
    )
    return (
        prelude
        + script[exec_start:exec_end]
        + script[staging_start:staging_end]
        + script[helpers_start:helpers_end]
        + script[compose_start:compose_end]
    )


def git(cwd, *args, check=True):
    result = subprocess.run(
        [GIT, *args], cwd=cwd, capture_output=True, text=True,
        env={**os.environ, "GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t",
             "GIT_COMMITTER_NAME": "t", "GIT_COMMITTER_EMAIL": "t@t"},
    )
    if check and result.returncode != 0:
        raise AssertionError(f"git {' '.join(args)} failed: {result.stderr}")
    return result.stdout.strip()


def write(path, text):
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w", encoding="utf-8", newline="\n") as handle:
        handle.write(text)


class Repo:
    """A bare origin with a preview branch, plus a working clone on the run's work branch."""

    def __init__(self, root):
        self.root = root
        self.origin = os.path.join(root, "origin.git")
        self.work = os.path.join(root, "work")
        self.other = os.path.join(root, "other")
        git(root, "init", "-q", "--bare", "-b", "preview", self.origin)
        seed = os.path.join(root, "seed")
        git(root, "clone", "-q", self.origin, seed)
        for name in (seed,):
            git(name, "config", "user.name", "t")
            git(name, "config", "user.email", "t@t")
        git(seed, "checkout", "-q", "-b", "preview")
        write(os.path.join(seed, "app.txt"), "one\ntwo\nthree\n")
        write(os.path.join(seed, ".gitignore"), "p2hs/\n")  # manifests are untracked/ignored
        git(seed, "add", "-A")
        git(seed, "commit", "-q", "-m", "base")
        git(seed, "push", "-q", "origin", "preview")
        self.base = git(seed, "rev-parse", "HEAD")
        git(root, "clone", "-q", self.origin, self.work)
        git(root, "clone", "-q", self.origin, self.other)
        for name in (self.work, self.other):
            git(name, "config", "user.name", "t")
            git(name, "config", "user.email", "t@t")
        git(self.work, "checkout", "-q", "-b", BRANCH, "origin/preview")

    def origin_rev(self, ref):
        return git(self.origin, "rev-parse", ref)

    def origin_has(self, ref, path):
        return git(self.origin, "cat-file", "-e", f"{ref}:{path}", check=False) == "" and \
            subprocess.run([GIT, "cat-file", "-e", f"{ref}:{path}"], cwd=self.origin,
                           capture_output=True).returncode == 0

    def preview_moves(self, path, text):
        """Someone else advances preview while the run is in progress."""
        git(self.other, "pull", "-q", "origin", "preview")
        write(os.path.join(self.other, path), text)
        git(self.other, "add", "-A")
        git(self.other, "commit", "-q", "-m", f"other run touches {path}")
        git(self.other, "push", "-q", "origin", "HEAD:preview")


def run_node(repo: Repo, body: str) -> dict:
    source = _extract() + f"\nconst payload = {{ run_id: {json.dumps(RUN_ID)}, branch: 'preview' }};\n" + body
    with tempfile.TemporaryDirectory() as directory:
        harness = os.path.join(directory, "harness.mjs")
        with open(harness, "w", encoding="utf-8") as handle:
            handle.write(source)
        done = subprocess.run([NODE, harness], cwd=repo.work, capture_output=True, text=True, timeout=120)
    assert done.returncode == 0, done.stderr
    line = next(l for l in reversed(done.stdout.splitlines()) if l.startswith("RESULT "))
    return json.loads(line[len("RESULT "):])


@pytest.fixture
def repo():
    with tempfile.TemporaryDirectory() as root:
        yield Repo(root)


def agent_commit(repo, path="index.html", text="<h1>site</h1>\n"):
    write(os.path.join(repo.work, path), text)
    git(repo.work, "add", "-A")
    git(repo.work, "commit", "-q", "-m", "agent output")


# ---- checkpoints ---------------------------------------------------------------------------


def test_a_checkpoint_saves_the_agents_work_and_the_untracked_manifests_to_the_run_branch(repo):
    agent_commit(repo)
    write(os.path.join(repo.work, "p2hs", "feature-packs.json"), '{"compose": []}\n')
    assert git(repo.work, "status", "--porcelain") == ""  # ignored, i.e. invisible to a normal commit
    out = run_node(repo, f"""
      const sha = await pushCheckpoint(payload, {json.dumps(BRANCH)}, 'agent output');
      console.log('RESULT ' + JSON.stringify({{ sha }}));
    """)
    assert out["sha"] == repo.origin_rev(BRANCH) == git(repo.work, "rev-parse", "HEAD")
    assert repo.origin_has(BRANCH, "index.html")
    assert repo.origin_has(BRANCH, "p2hs/feature-packs.json")  # so a resumed run has it to validate
    assert repo.origin_rev("preview") == repo.base  # preview is untouched


def test_checkpoints_only_move_the_branch_forward(repo):
    agent_commit(repo)
    first = run_node(repo, f"""
      console.log('RESULT ' + JSON.stringify({{ sha: await pushCheckpoint(payload, {json.dumps(BRANCH)}, 'one') }}));
    """)["sha"]
    agent_commit(repo, "about.html", "<h1>about</h1>\n")
    second = run_node(repo, f"""
      console.log('RESULT ' + JSON.stringify({{ sha: await pushCheckpoint(payload, {json.dumps(BRANCH)}, 'two') }}));
    """)["sha"]
    assert repo.origin_rev(BRANCH) == second != first
    ancestor = subprocess.run([GIT, "merge-base", "--is-ancestor", first, second], cwd=repo.origin)
    assert ancestor.returncode == 0  # linear history: nothing was rewritten or forced


def test_a_checkpoint_never_force_pushes_over_someone_elses_commit(repo):
    agent_commit(repo)
    # a diverging commit lands on the run branch from elsewhere
    git(repo.other, "fetch", "-q", "origin")
    git(repo.other, "checkout", "-q", "-b", "elsewhere", "origin/preview")
    write(os.path.join(repo.other, "intruder.txt"), "x\n")
    git(repo.other, "add", "-A")
    git(repo.other, "commit", "-q", "-m", "intruder")
    git(repo.other, "push", "-q", "origin", f"HEAD:refs/heads/{BRANCH}")
    intruder = repo.origin_rev(BRANCH)
    out = run_node(repo, f"""
      console.log('RESULT ' + JSON.stringify({{ sha: await pushCheckpoint(payload, {json.dumps(BRANCH)}, 'agent output') }}));
    """)
    assert out["sha"] is None  # reported as not saved...
    assert repo.origin_rev(BRANCH) == intruder  # ...and the remote was not overwritten


# ---- promotion --------------------------------------------------------------------------------


def test_promotion_fast_forwards_preview_when_it_has_not_moved(repo):
    agent_commit(repo)
    out = run_node(repo, "console.log('RESULT ' + JSON.stringify(await promoteToPreview(payload)));")
    assert out == {"ok": True, "merged": False}
    assert repo.origin_rev("preview") == git(repo.work, "rev-parse", "HEAD")


def test_promotion_merges_when_checkout_never_fetched_preview(repo):
    """Actions checks out only the work branch, so origin/preview is not a local ref."""
    agent_commit(repo)
    repo.preview_moves("other.txt", "someone else's change\n")
    git(repo.work, "update-ref", "-d", "refs/remotes/origin/preview")
    out = run_node(repo, "console.log('RESULT ' + JSON.stringify(await promoteToPreview(payload)));")
    assert out["ok"] is True and out["merged"] is True
    assert repo.origin_has("preview", "other.txt") and repo.origin_has("preview", "index.html")


def test_promotion_merges_a_compatible_preview_change_instead_of_overwriting_it(repo):
    agent_commit(repo)
    repo.preview_moves("other.txt", "someone else's change\n")
    moved = repo.origin_rev("preview")
    out = run_node(repo, "console.log('RESULT ' + JSON.stringify(await promoteToPreview(payload)));")
    assert out == {"ok": True, "merged": True}
    new_preview = repo.origin_rev("preview")
    assert repo.origin_has("preview", "other.txt") and repo.origin_has("preview", "index.html")
    assert subprocess.run([GIT, "merge-base", "--is-ancestor", moved, new_preview],
                          cwd=repo.origin).returncode == 0  # their commit is still in history


def test_a_conflicting_preview_change_is_reported_and_nothing_is_touched(repo):
    write(os.path.join(repo.work, "app.txt"), "one\nTWO (mine)\nthree\n")
    git(repo.work, "add", "-A")
    git(repo.work, "commit", "-q", "-m", "agent edits app.txt")
    repo.preview_moves("app.txt", "one\nTWO (theirs)\nthree\n")
    preview_before = repo.origin_rev("preview")
    head_before = git(repo.work, "rev-parse", "HEAD")
    out = run_node(repo, "console.log('RESULT ' + JSON.stringify(await promoteToPreview(payload)));")
    assert out["ok"] is False and out["reason"] == "preview_diverged"
    assert repo.origin_rev("preview") == preview_before  # preview unchanged
    assert git(repo.work, "rev-parse", "HEAD") == head_before  # merge aborted, HEAD unchanged
    assert git(repo.work, "status", "--porcelain") == ""  # and the tree is clean


def test_promotion_without_a_target_branch_is_refused(repo):
    out = run_node(repo, "console.log('RESULT ' + JSON.stringify(await promoteToPreview({ run_id: 'x' })));")
    assert out == {"ok": False, "reason": "no_target_branch"}


# ---- trust ------------------------------------------------------------------------------------


def test_only_this_runs_own_branch_name_is_trusted(repo):
    out = run_node(repo, f"""
      const good = workBranchOf({{ run_id: {json.dumps(RUN_ID)}, work_branch_name: {json.dumps(BRANCH)} }});
      const other = workBranchOf({{ run_id: {json.dumps(RUN_ID)}, work_branch_name: 'p2hs/codegen/{uuid4()}' }});
      const preview = workBranchOf({{ run_id: {json.dumps(RUN_ID)}, work_branch_name: 'preview' }});
      const missing = workBranchOf({{ run_id: {json.dumps(RUN_ID)} }});
      activeWork = {{ branch: good, checkpoint: 'agent_output', sha: 'a'.repeat(40) }};
      console.log('RESULT ' + JSON.stringify({{ good, other, preview, missing, fields: workFields() }}));
    """)
    assert out["good"] == BRANCH
    assert out["other"] is None and out["preview"] is None and out["missing"] is None
    assert out["fields"] == {"work_branch": BRANCH, "checkpoint": "agent_output", "checkpoint_sha": "a" * 40}


def test_the_runner_still_parses_with_the_work_branch_flow():
    script = build_codegen_actions_runner()
    for marker in ("promoteToPreview(payload)", "pushCheckpoint(payload, workBranch,",
                   "const skipAgent = Boolean(workBranch", "changesBase", "deploy_branch="):
        assert marker in script, marker
    with tempfile.TemporaryDirectory() as directory:
        path = os.path.join(directory, "runner.mjs")
        with open(path, "w", encoding="utf-8") as handle:
            handle.write(script)
        assert subprocess.run([NODE, "--check", path], capture_output=True, text=True).returncode == 0
