"""ORCH-058 TC-07/08: static + caller-contract guarantees of the provenance plumbing.

These assert the *shape* of the deploy artefacts that can't be unit-tested by
running them (they shell out to docker/ssh on the host):

  * TC-07 — the deploy hook fail-closes BEFORE `docker tag` when the staging
            image's git-revision label != EXPECTED_REVISION (exit 1), and the
            new `--build-staging` rebuild mode (a) stamps GIT_SHA into the image,
            (b) uses $BUILD_CONTEXT as the build context, (c) recreates 8501 +
            health-checks, (d) runs staging_check against the FRESH image
            (Strategy A step 3, AC-4), and (e) never recomputes GIT_SHA from $REPO.
  * TC-08 — the Dockerfile declares `ARG GIT_SHA` and stamps it into the
            `org.opencontainers.image.revision` OCI label (the anchor B reads).
  * TC-09 — the caller↔hook contract: `rebuild_staging_image` invokes the hook
            in `--build-staging` mode with BUILD_CONTEXT=<host-worktree>,
            GIT_SHA=<validated sha>, and an EXPLICIT staging target (never prod).
"""

import pathlib

_ROOT = pathlib.Path(__file__).resolve().parents[1]
_HOOK = _ROOT / "scripts" / "orchestrator-deploy-hook.sh"
_DOCKERFILE = _ROOT / "Dockerfile"


# ---------------------------------------------------------------------------
# TC-07: hook fail-closed provenance guard + --build-staging rebuild mode
# ---------------------------------------------------------------------------
def test_tc07_hook_has_fail_closed_provenance_guard():
    text = _HOOK.read_text(encoding="utf-8")
    # The label key the hook inspects must be the OCI revision label.
    assert 'REVISION_LABEL="org.opencontainers.image.revision"' in text
    # EXPECTED_REVISION is read (default unset -> backward compatible).
    assert 'EXPECTED_REVISION="${EXPECTED_REVISION:-}"' in text
    # The guard must inspect the source image's label and normalise <no value>.
    assert "docker image inspect --format" in text
    assert '"<no value>"' in text
    # Fail-closed: empty OR mismatch -> abort with exit 1.
    assert '-z "$IMG_REV" || "$IMG_REV" != "$EXPECTED_REVISION"' in text


def test_tc07_provenance_guard_precedes_docker_tag():
    """The fail-closed `exit 1` must sit BEFORE the `docker tag` retag line."""
    text = _HOOK.read_text(encoding="utf-8")
    guard = text.index("$EXPECTED_REVISION")
    retag = text.index('docker tag "$SOURCE_IMAGE" "$TARGET_IMAGE"')
    assert guard < retag, "provenance guard must run before the prod retag"


def test_tc07_build_staging_mode_stamps_git_sha():
    text = _HOOK.read_text(encoding="utf-8")
    # The new Strategy-A rebuild mode exists and is keyed on --build-staging.
    assert '"${1:-}" == "--build-staging"' in text
    # It rebuilds the staging image stamping the validated commit as a build-arg.
    assert 'docker build --build-arg GIT_SHA="$GIT_SHA"' in text


def test_tc07_build_staging_uses_build_context_and_recreates_8501():
    """The rebuild must use $BUILD_CONTEXT as the docker build context and recreate
    the staging service with a health-check (not a bare build)."""
    text = _HOOK.read_text(encoding="utf-8")
    # $BUILD_CONTEXT is the build context of the rebuild (validated worktree).
    assert 'docker build --build-arg GIT_SHA="$GIT_SHA" -t "$TARGET_IMAGE" "$BUILD_CONTEXT"' in text
    # Recreate the staging service on the fresh image (no-build) + health-check.
    assert 'up -d --no-build "$TARGET_SERVICE"' in text
    assert 'health_check 10 6 "build-staging-health"' in text


def test_tc07_build_staging_does_not_recompute_git_sha_from_repo():
    """Regression guard (root cause of the silent-stale-promote class): the
    --build-staging mode must NOT derive GIT_SHA itself from the prod $REPO clone —
    it must consume the GIT_SHA passed in by the caller (the validated commit)."""
    text = _HOOK.read_text(encoding="utf-8")
    # Anchor on the actual block guard (not the header comment mentions).
    after = text[text.index('"${1:-}" == "--build-staging"'):]
    assert 'GIT_SHA="${GIT_SHA:-}"' in after
    assert "git rev-parse" not in after, "GIT_SHA must come from the caller, not the prod clone"


def test_tc07_build_staging_runs_staging_check_against_fresh_image():
    """Strategy A step 3 (ADR-001, AC-4): after recreate+health, the FRESH image is
    validated by staging_check.py (not health-only). This is the P1 the reviewer
    flagged: validate exactly the artefact later retagged to prod."""
    text = _HOOK.read_text(encoding="utf-8")
    # Anchor on the actual block guard (not the header comment mentions).
    after = text[text.index('"${1:-}" == "--build-staging"'):]
    # staging_check is invoked, inside the staging container, --mode stub by default.
    assert "staging_check.py" in after
    assert 'docker exec "$STAGING_CONTAINER"' in after
    assert '--mode "$STAGING_CHECK_MODE"' in after
    assert 'STAGING_CHECK_MODE="${STAGING_CHECK_MODE:-stub}"' in after
    # The staging_check run must come AFTER the health-check (health gates readiness).
    assert after.index('health_check 10 6 "build-staging-health"') < after.index("staging_check.py")


# ---------------------------------------------------------------------------
# TC-08: Dockerfile stamps the OCI revision label from a build-arg
# ---------------------------------------------------------------------------
def test_tc08_dockerfile_stamps_revision_label():
    text = _DOCKERFILE.read_text(encoding="utf-8")
    assert "ARG GIT_SHA" in text
    assert "LABEL org.opencontainers.image.revision=$GIT_SHA" in text


# ---------------------------------------------------------------------------
# TC-08b (ORCH-021 regression): the Dockerfile must not COPY a gitignored path.
# The ORCH-058 staging rebuild builds with the task *worktree* as the docker build
# context. A fresh worktree contains only tracked files, so any `COPY <gitignored>`
# (notably `data/`, the SQLite dir) makes `docker build` fail with exit 1 and bounces
# the task off `deploy-staging`. `data/` is a runtime bind-mount volume anyway, so it
# must never be a COPY source.
# ---------------------------------------------------------------------------
def test_tc08b_dockerfile_does_not_copy_gitignored_data_dir():
    text = _DOCKERFILE.read_text(encoding="utf-8")
    gitignore = (_ROOT / ".gitignore").read_text(encoding="utf-8").splitlines()
    # Precondition: `data/` really is gitignored (the build context will not have it).
    assert "data/" in [ln.strip() for ln in gitignore]
    # The Dockerfile must not COPY it (would break the worktree-context staging build).
    copy_sources = [
        line.split()[1]
        for line in text.splitlines()
        if line.strip().upper().startswith("COPY") and len(line.split()) >= 3
    ]
    assert "data/" not in copy_sources, (
        "Dockerfile must not `COPY data/` — it's gitignored and absent from the "
        "worktree build context used by the ORCH-058 staging rebuild (exit 1)."
    )


# ---------------------------------------------------------------------------
# TC-09: caller↔hook contract — rebuild_staging_image builds the right command
# ---------------------------------------------------------------------------
def test_tc09_rebuild_staging_image_passes_validated_context_and_staging_target(monkeypatch):
    """`rebuild_staging_image` must invoke the hook `--build-staging` over ssh with
    BUILD_CONTEXT=<host-worktree>, GIT_SHA=<validated sha>, and an EXPLICIT staging
    target (service/port/profile/container) — never the prod 8500 target. The absence
    of this contract test is what hid the earlier P0s (review P2)."""
    import src.image_freshness as imgf

    captured = {}

    class _FakeCompleted:
        returncode = 0
        stdout = ""
        stderr = ""

    def _fake_run(cmd, *a, **kw):
        captured["cmd"] = cmd
        return _FakeCompleted()

    monkeypatch.setattr(imgf, "_ssh_target", lambda: "slin@host")
    monkeypatch.setattr(imgf, "_host_worktree_path",
                        lambda repo, branch: "/home/slin/repos/_wt/orchestrator/feature_X")
    monkeypatch.setattr(imgf.subprocess, "run", _fake_run)

    ok, msg = imgf.rebuild_staging_image("orchestrator", "feature/ORCH-058", "abc123def456")
    assert ok, msg

    cmd = captured["cmd"]
    assert cmd[0] == "ssh"
    inner = cmd[-1]  # the remote shell command string
    # Validated commit + validated worktree as build context.
    assert "GIT_SHA=abc123def456" in inner
    assert "BUILD_CONTEXT=/home/slin/repos/_wt/orchestrator/feature_X" in inner
    # Explicit STAGING target — never the prod 8500 service/port.
    assert "TARGET_SERVICE=orchestrator-staging" in inner
    assert "TARGET_PORT=8501" in inner
    assert "COMPOSE_PROFILE=staging" in inner
    assert "STAGING_CONTAINER=orchestrator-staging" in inner
    assert "orchestrator-orchestrator-staging" in inner  # staging TARGET_IMAGE
    assert "--build-staging" in inner
    # Hard safety: the prod service/port must NOT leak into the staging rebuild.
    assert "TARGET_PORT=8500" not in inner
    assert "TARGET_SERVICE=orchestrator " not in inner


def test_tc09_rebuild_staging_image_no_ssh_host_fails_closed(monkeypatch):
    """No ssh host configured -> never-raise, fail-closed (False), no command run."""
    import src.image_freshness as imgf

    monkeypatch.setattr(imgf, "_ssh_target", lambda: None)
    ok, reason = imgf.rebuild_staging_image("orchestrator", "feature/ORCH-058", "abc123")
    assert ok is False
    assert "ssh host" in reason
