"""Tests for GitHub Actions codegen payload helpers."""
import hashlib
import hmac

import pytest
import yaml
from src.domain.codegen.actions_pipeline import (
    _json_dumps,
    _sign_payload,
    build_codegen_actions_runner,
    build_codegen_actions_workflow,
)


def test_payload_signature_uses_stable_json():
    payload = {"b": 2, "a": {"z": True, "m": ["x"]}}
    expected_body = '{"a":{"m":["x"],"z":true},"b":2}'
    assert _json_dumps(payload, pretty=False) == expected_body
    digest = hmac.new(b"secret", expected_body.encode("utf-8"), hashlib.sha256).hexdigest()
    assert _sign_payload(payload, "secret") == f"sha256={digest}"


def test_codegen_actions_workflow_accepts_dispatch_payload():
    workflow = build_codegen_actions_workflow(project_type="nuxt_app", project_ref="pabc12345")
    assert "workflow_dispatch:" in workflow
    assert "payload_path:" in workflow
    assert "cursor_model:" in workflow
    assert "continuation_attempt:" in workflow
    assert "curl https://cursor.com/install -fsS | bash" in workflow
    assert "curl -fsSL https://chatgpt.com/codex/install.sh | sh" in workflow
    assert "curl -fsSL https://claude.ai/install.sh | bash" in workflow
    assert "CODEGEN_PROVIDER" in workflow
    assert "scripts/p2hs-codegen-action-runner.mjs" in workflow
    assert "Deploy to Cloudflare Pages" not in workflow
    assert "timeout-minutes: 30" in workflow


def test_codegen_actions_runner_measures_the_request_after_platform_sync():
    runner = build_codegen_actions_runner()

    assert "function parseCopyRequest(prompt)" not in runner
    assert '["diff", "--name-only", `${baseHead}..HEAD`]' in runner
    assert "isPlatformProjectionPath" in runner
    assert "record_adapter_files" in runner


def test_codegen_actions_runner_rejects_internal_only_changes():
    runner = build_codegen_actions_runner()

    assert "restoreEphemeralCodegenFiles" in runner
    assert "customer-visible file changes" in runner
    assert '["add", "-A"]' not in runner
    assert "stagePublishableAndAssert" in runner
    assert "physical_cloud_hostname" in runner


def test_codegen_actions_runner_verifies_core_composition_materialization():
    runner = build_codegen_actions_runner()

    assert "verifyCoreComposition" in runner
    assert "P2HS_COMPOSITION_MISMATCH" in runner
    assert "app-blocks-plan_missing" in runner
    assert "feature-packs_missing" in runner


def test_codegen_actions_runner_embeds_auth_guard_without_core_imports():
    runner = build_codegen_actions_runner()

    assert "def assert_generated_auth_conformance" in runner
    assert "def canonical_generation_plan_required" in runner
    assert "def load_generation_plan_artifact" in runner


@pytest.mark.parametrize("project_type", ["html", "react_app", "nuxt_app"])
def test_codegen_actions_workflow_does_not_embed_a_second_deploy(project_type):
    workflow = build_codegen_actions_workflow(project_type=project_type, project_ref="pabc12345")
    jobs = yaml.safe_load(workflow)["jobs"]
    assert "deploy" not in jobs
    assert "Deploy to Cloudflare Pages" not in workflow
    assert jobs["codegen"]["timeout-minutes"] == 30


def test_codegen_actions_runner_saves_and_restarts_before_job_timeout():
    runner = build_codegen_actions_runner()

    assert "needs_continuation" in runner
    assert "writeGithubOutput" in runner
    assert "saveDeployArtifact" in runner
    assert "RUNNER_TEMP" in runner
    assert "Authored by P2HS only" in runner
    assert "attributeCommitsToAgent: false" in runner
    assert "installP2hsGitHook" in runner
    assert "--resume" in runner
    assert "agent_budget_seconds" in runner
    assert "continued=${continued ? \"true\" : \"false\"}" in runner
    assert "await writeGithubOutput({ commitSha, continued: timedOut, deployRequired" in runner


def test_codegen_actions_runner_rewrites_cursor_trailers():
    runner = build_codegen_actions_runner()

    assert "Co-authored-by" in runner
    assert "rewriteCommitsAuthoredByP2hs" in runner
    assert "commit-msg-hook.py" in runner
    assert "mkdtemp" in runner
    assert "tmpdir()" in runner
    assert "import fs from \"node:fs\"" in runner
    assert 'const fs = require("fs")' not in runner
    assert ".p2hs/local-bak" in runner
    assert "purgeScannedBackups" in runner


def test_codegen_actions_runner_supports_openai_codex_cli():
    runner = build_codegen_actions_runner()

    assert 'provider === "codex"' in runner
    assert '"exec", "--sandbox", "danger-full-access"' in runner
    assert "CODEX_API_KEY" in runner
    assert "OPENAI_API_KEY" in runner
    assert "OpenAI Codex CLI" in runner
    assert 'input: ""' in runner
    assert "const selectedModel = provider" in runner


def test_codegen_actions_runner_supports_claude_code_cli():
    runner = build_codegen_actions_runner()

    assert 'provider === "claude"' in runner
    assert '"--dir", process.cwd(), "--message"' in runner
    assert "ANTHROPIC_API_KEY" in runner
    assert "Claude Code CLI" in runner
    assert 'input: ""' in runner


def test_codegen_actions_workflow_uses_node_22():
    workflow = build_codegen_actions_workflow(project_type="nuxt_app", project_ref="pabc12345")

    assert "node-version: 20" not in workflow
    assert "node-version: 22" in workflow
    assert workflow.count("node-version: 22") == 1


def test_codegen_actions_runner_keeps_cursor_prompt_off_argv():
    runner = build_codegen_actions_runner()

    assert "args.push(combinedPrompt)" not in runner
    assert ".p2hs/ai-context/agent-prompt.md" in runner
    assert "complete task prompt for this run" in runner
    assert "prompt_chars=${combinedPrompt.length}" in runner


def test_codegen_actions_runner_strips_auth_from_provision_payload():
    runner = build_codegen_actions_runner()
    assert "function provisionPayload(manifest)" in runner
    # The wrapped {provision, pack_installs, pack_compositions} body sent to Core's
    # pre-push gate carries only provisionPayload()'s sanitized output, never the raw
    # parsed manifest -- resolveCapabilitiesBeforePush replaced provisionBeforePush's
    # narrower provision-only JSON.stringify(declaration) call, same guarantee.
    assert "JSON.stringify(body)" in runner
    assert "JSON.stringify(provisionManifest)" not in runner
    assert "JSON.stringify(packsManifest)" not in runner
    assert "const pendingProvision = provisionPayload(pendingProvisionFile)" in runner
    assert "Required platform resource provisioning failed" in runner


def test_codegen_actions_runner_reports_callback_auth_mismatch_immediately():
    runner = build_codegen_actions_runner()
    assert "Core capability callback authorization failed (HTTP ${response.status})" in runner
    assert "Verify CODEGEN_ACTIONS_CALLBACK_TOKEN in Core matches" in runner


def test_codegen_actions_runner_normalizes_callback_tokens_before_bearer_encoding():
    runner = build_codegen_actions_runner()
    assert "function normalizeCallbackToken(value)" in runner
    assert 'token.slice(0, 7).toLowerCase() === "bearer "' in runner
    assert "normalizeCallbackToken(payload?.callback_grant)" in runner
    assert "normalizeCallbackToken(process.env.P2HS_CODEGEN_CALLBACK_TOKEN)" in runner


def test_codegen_actions_runner_always_applies_cloud_adapters():
    runner = build_codegen_actions_runner()

    assert "installPlatformAdapters" in runner
    assert '"--migrate"' in runner or "--migrate" in runner
    assert "cloudCutover" in runner
    assert "/cloud-cutover" in runner
    assert "p2hs-auth-users" in runner
    assert "p2hs-auth-password" in runner
    assert "p2hs-records" in runner
    assert "forgot-password" in runner
    assert "legacy_users: leftoverUsers" in runner
    assert "if (!existsSync(normalized))" not in runner
    assert "await writeFile(normalized, String(content), \"utf8\")" in runner


def test_codegen_actions_runner_restores_locked_port_artifacts_from_baseline():
    runner = build_codegen_actions_workflow("nuxt_app", "pabc12345") + build_codegen_actions_runner()

    assert "restoreLockedPortArtifacts" in runner
    assert "p2hs/platform-ports.lock.json" in runner
    assert "await restoreLockedPortArtifacts(baseHead, payload)" in runner


def test_codegen_actions_runner_restores_ephemeral_from_base_and_retires_stores():
    runner = build_codegen_actions_runner()

    assert "__P2HS_SHADOW_STUBS__" not in runner
    assert "retireShadowStores" in runner
    assert "__P2HS_RETIRED_LOCAL_STORE__" in runner
    assert "restoreEphemeralCodegenFiles(baseHead)" in runner
    assert '["cat-file", "-e"' in runner
    assert "working tree is not clean" in runner
    assert 'python3 - "$1" <<\'PY\'' in runner
    assert "python3 .p2hs/commit-msg-hook.py" not in runner


def test_codegen_actions_runner_auto_corrects_generated_auth_transport_findings():
    runner = build_codegen_actions_runner()

    assert "Generated auth conformance failed:" in runner
    assert "generated-auth-conformance" in runner
    assert 'failure_class: "invalid_requirement"' in runner
    assert 'activeWork.checkpoint = "agent_output"' in runner
