"""Channel dispatch names an exact pushed Git commit; CI allocates and builds."""
import subprocess

import pytest


def git(repo, *args):
    return subprocess.check_output(["git", *args], cwd=repo, text=True, encoding="utf-8").strip()


@pytest.fixture
def source(tmp_path):
    origin = tmp_path / "origin.git"
    repo = tmp_path / "source"
    origin.mkdir()
    repo.mkdir()
    git(origin, "init", "--bare", "--quiet")
    git(repo, "init", "--initial-branch=main", "--quiet")
    git(repo, "config", "user.name", "Test")
    git(repo, "config", "user.email", "test@example.test")
    (repo / "pyproject.toml").write_text('[project]\nname="fixture"\nversion="1.2.3"\n', encoding="utf-8")
    git(repo, "add", "pyproject.toml")
    git(repo, "commit", "--quiet", "-m", "Initial")
    git(repo, "remote", "add", "origin", str(origin))
    git(repo, "push", "--quiet", "-u", "origin", "main")
    return repo


def test_dispatch_names_channel_and_binds_to_pushed_source(source):
    from scripts.releases.channel_build import prepare_build
    calls = []
    options = dict(name="custom-branch", revision="main", remote="origin", repo=source,
                   repository="example/hermes-agent", default_branch="main",
                   dispatch=calls.append, bundle_env={"HERMES_GUEST_ONBOARDING": "1"})
    dry = prepare_build(**options)
    assert dry["commit"] == git(source, "rev-parse", "HEAD")
    assert dry["sourceVersion"] == "1.2.3"
    assert not calls
    command = dry["command"]
    assert "desktop-bundled-release.yml" in command
    assert "channel=custom-branch" in command
    assert "build_commit=" + dry["commit"] in command
    assert any(value.startswith("bundle_env=") for value in command)
    assert "--ref" in command and command[command.index("--ref") + 1] == "main"
    assert not any(value.startswith("branding=") for value in command)
    stable = prepare_build(**options, branding="stable")
    assert "branding=stable" in stable["command"] and stable["branding"] == "stable"
    admitted = prepare_build(**options, publish=True)
    assert calls == [command]
    assert admitted["commit"] == dry["commit"]
    # An unpublished commit is never dispatched.
    git(source, "commit", "--allow-empty", "--quiet", "-m", "Unpublished")
    with pytest.raises(ValueError, match="pushed"):
        prepare_build(**options, publish=True)
    assert len(calls) == 1


def test_missing_default_branch_is_rejected_without_dispatch(source):
    from scripts.releases.channel_build import prepare_build
    from hermes_cli.release_channels import ChannelError
    with pytest.raises(ChannelError, match="default branch"):
        prepare_build(name="invalid-controller", revision="main", remote="origin", repo=source,
                      repository="example/hermes-agent", default_branch="",
                      dispatch=lambda command: pytest.fail("must not dispatch"), publish=True)


def test_disposable_scope_is_opt_in_and_lease_bound(monkeypatch):
    from scripts.releases import channel_build, r2
    monkeypatch.delenv("GITHUB_ACTIONS", raising=False)
    monkeypatch.delenv("GITHUB_REPOSITORY", raising=False)
    monkeypatch.delenv("R2_DISPOSABLE_RUN", raising=False)
    monkeypatch.setenv("CLOUDFLARE_R2_PUBLIC_URL", "https://archive.example")
    # No lease and no repository identity check: scoping is opt-in, the
    # unscoped publisher talks production keys only when the caller holds them.
    monkeypatch.setattr(r2, "credentials", lambda: ({"access_key_id": "inert", "secret_key": "inert"}, "https://r2.example", "bucket"))
    unscoped = channel_build.configured_publisher("ethernet8023/hermes-agent")
    assert unscoped.store.scope.key("releases/channels/preview.json") == "releases/channels/preview.json"
    monkeypatch.setenv("R2_DISPOSABLE_RUN", "123")
    monkeypatch.setenv("GITHUB_REPOSITORY_ID", "456")
    monkeypatch.setattr(channel_build.commit_build, "output", lambda args: "789")
    with pytest.raises(ValueError, match="another repository"):
        channel_build.configured_publisher("ethernet8023/hermes-agent")
    monkeypatch.setattr(channel_build.commit_build, "output", lambda args: "456")
    publisher = channel_build.configured_publisher("ethernet8023/hermes-agent")
    assert publisher.store.scope.key("releases/channels/preview.json") == "ci-disposable/456/123/releases/channels/preview.json"
    assert publisher.reader.base_url == "https://archive.example/ci-disposable/456/123"


def test_release_parser_preserves_plain_oneoff_dispatch(monkeypatch):
    import sys
    from scripts import release
    from scripts.releases import commit_build
    calls = []
    monkeypatch.setattr(commit_build, "cmd_build_commit", lambda args: calls.append(("oneoff", args.build_commit)))
    monkeypatch.setattr(sys, "argv", ["release.py", "--build-commit", "main"])
    release.main()
    assert calls == [("oneoff", "main")]
    monkeypatch.setattr(sys, "argv", ["release.py", "--channel", "bad/name", "--build-commit", "main"])
    with pytest.raises(SystemExit) as rejected:
        release.main()
    assert rejected.value.code == 2
    from scripts.releases import channel_build
    monkeypatch.setattr(channel_build, "cmd_channel", lambda args: calls.append(("channel", args.channel)))
    monkeypatch.setattr(sys, "argv", ["release.py", "--channel", "unknown-name", "--build-commit", "main"])
    release.main()
    assert calls[-1] == ("channel", "unknown-name")
    monkeypatch.setattr(sys, "argv", ["release.py", "--channels", "--bundle-env", "X=y"])
    with pytest.raises(SystemExit) as incompatible:
        release.main()
    assert incompatible.value.code == 2
    monkeypatch.setattr(sys, "argv", ["release.py", "--branding", "stable", "--build-commit", "main"])
    with pytest.raises(SystemExit) as orphan:
        release.main()
    assert orphan.value.code == 2
    monkeypatch.setattr(sys, "argv", ["release.py", "--channel", "unknown-name", "--branding", "stable", "--build-commit", "main"])
    release.main()
    assert calls[-1] == ("channel", "unknown-name")
