"""Run-scoped state files — M2(c) live-verify regression (not one of the 19 plan cases).

The four CCCI state files (deploys countfile, opstate, deps, depskip) must be keyed by
run id + harness pid, NEVER by app domain: a second run of the SAME domain executes its
main() preamble (state-file init, deploy_app's _record_deploy) BEFORE it blocks at the
app lock, so domain-keyed files in the shared tempdir get reset/removed underneath the
live first run. Observed live (builds 279/281): false DG4.1 deploy-count=2 in run 1,
countfile FileNotFoundError crash in run 2. Children never re-derive these paths — they
receive them via the CCCI_*_FILE env vars, so per-process uniqueness is sufficient.
"""

from __future__ import annotations

import os
import sys
import tempfile

sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
import run_recipe_ci  # noqa: E402
from concutil import wait_marker  # noqa: E402

DOMAIN = "fake-abc123.ci.commoninternet.net"


def test_20_state_paths_keyed_by_run_and_pid_never_by_domain(monkeypatch):
    domain = "immi-ad3e33.ci.commoninternet.net"
    monkeypatch.setenv("CCCI_APP_DOMAIN", domain)

    monkeypatch.setenv("DRONE_BUILD_NUMBER", "279")
    p279 = run_recipe_ci._run_state_path("deploys")
    monkeypatch.setenv("DRONE_BUILD_NUMBER", "281")
    p281 = run_recipe_ci._run_state_path("deploys")

    # the double-!testme invariant: two runs (same domain) share NO state file
    assert p279 != p281
    # keyed by run id + pid, under the tempdir
    base = os.path.basename(p279)
    assert base == f"ccci-deploys-279-{os.getpid()}"
    assert os.path.dirname(p279) == tempfile.gettempdir()
    # the app domain must not appear in the path at all
    assert domain not in p279 and domain not in p281


def test_20c_same_domain_runs_each_keep_their_own_count(tmp_path, lock_dir, pool):
    """The live CONC-A1 interleaving, with REAL processes + the REAL lock and counter code:
    run A holds the app lock; run B (same domain) fires its pre-lock _record_deploy and
    blocks; A then reads its counter — must still be 1 (not polluted by B) — and removes
    its own file; B acquires and must find ITS file intact (no FileNotFoundError)."""
    gate = tmp_path / "gate"
    env_a = {"TMPDIR": str(tmp_path), "DRONE_BUILD_NUMBER": "9001"}
    env_b = {"TMPDIR": str(tmp_path), "DRONE_BUILD_NUMBER": "9002"}

    pa, out_a = pool.spawn("deploy-count-run", DOMAIN, str(gate), env_extra=env_a)
    assert wait_marker(out_a, "ACQUIRED")
    pb, out_b = pool.spawn("deploy-count-run", DOMAIN, "", env_extra=env_b)
    # B's main()-preamble + pre-lock increment have fired; B is now blocked on the app lock
    assert wait_marker(out_b, "PRELOCK")
    assert wait_marker(out_b, "ACQUIRED", timeout=1.0) is None  # still serialised behind A

    gate.touch()  # let A read its counter only AFTER B's pre-lock work landed
    line_a = wait_marker(out_a, "COUNT")
    assert line_a is not None and line_a.strip() == "COUNT 1", line_a  # not 2: B didn't pollute A
    pa.wait(timeout=15)

    line_b = wait_marker(out_b, "COUNT")
    assert line_b is not None and line_b.strip() == "COUNT 1", (
        line_b
    )  # B's file survived A's remove
    pb.wait(timeout=15)


def test_20b_manual_runs_distinct_via_pid(monkeypatch):
    # no DRONE_BUILD_NUMBER and no domain/run-id env → run_id() falls back to "manual";
    # the pid suffix still separates two concurrent hand-runs of the same domain.
    for var in ("DRONE_BUILD_NUMBER", "CCCI_APP_DOMAIN", "CCCI_RUN_ID"):
        monkeypatch.delenv(var, raising=False)
    p = run_recipe_ci._run_state_path("opstate")
    assert os.path.basename(p) == f"ccci-opstate-manual-{os.getpid()}"
