package gateway

import (
	"os"
	"strings"
	"testing"
)

func TestGatewayImageIsPinnedNonRootAndBinaryOnly(t *testing.T) {
	dockerfile, err := os.ReadFile("../Dockerfile.gateway")
	if err != nil {
		t.Fatal(err)
	}
	content := string(dockerfile)
	for _, required := range []string{
		"FROM gcr.io/distroless/static-debian12:nonroot@sha256:",
		"COPY --chown=nonroot:nonroot gateway-binary /usr/local/bin/gateway",
		"USER nonroot:nonroot",
		`ENTRYPOINT ["/usr/local/bin/gateway"]`,
		"org.opencontainers.image.revision",
	} {
		if !strings.Contains(content, required) {
			t.Fatalf("Dockerfile.gateway missing %q", required)
		}
	}
	for _, forbidden := range []string{"COPY . ", "ADD ", "latest", " apk ", " apt "} {
		if strings.Contains(content, forbidden) {
			t.Fatalf("Dockerfile.gateway contains forbidden image expansion %q", forbidden)
		}
	}
	for _, forbidden := range []string{"COPY --from=", "\nRUN "} {
		if strings.Contains(content, forbidden) {
			t.Fatalf("Dockerfile.gateway must remain a prebuilt-binary image, found %q", forbidden)
		}
	}
	dockerignore, err := os.ReadFile("../.dockerignore")
	if err != nil {
		t.Fatal(err)
	}
	if string(dockerignore) != "*\n!Dockerfile.gateway\n!gateway-binary\n" {
		t.Fatalf(".dockerignore permits an unexpected build context:\n%s", dockerignore)
	}
}

func TestGatewayExactHeadCIHasNoSecretAccess(t *testing.T) {
	workflow, err := os.ReadFile("../.github/workflows/go-ci.yml")
	if err != nil {
		t.Fatal(err)
	}
	content := string(workflow)
	start := strings.Index(content, "  gateway-head:\n")
	end := strings.Index(content, "\n  build-test:\n")
	if start < 0 || end <= start {
		t.Fatal("go-ci must contain a distinct Gateway exact-head job")
	}
	headJob := content[start:end]
	for _, required := range []string{
		"github.event_name == 'pull_request'",
		"github.event.pull_request.head.sha",
		"ref: ${{ env.SOURCE_SHA }}",
		"persist-credentials: false",
		"go test -race ./gateway/... ./cmd/gateway/...",
		"./scripts/check-gateway-boundary.sh",
		"docker build --quiet",
	} {
		if !strings.Contains(headJob, required) {
			t.Fatalf("Gateway exact-head CI missing %q", required)
		}
	}
	for _, forbidden := range []string{"secrets.", "DEPLOY_KEY", "go mod download"} {
		if strings.Contains(headJob, forbidden) {
			t.Fatalf("untrusted Gateway exact-head CI contains secret-bearing operation %q", forbidden)
		}
	}

	cleanup := strings.Index(content, "- name: Verify private credential cleanup")
	trustedImage := strings.LastIndex(content, "- name: Build independently runnable Gateway image")
	if cleanup < 0 || trustedImage <= cleanup {
		t.Fatal("trusted Gateway image build must remain after private credential cleanup")
	}
}

func TestGatewayTrustedPublisherIsSourceFreeAndPinned(t *testing.T) {
	caller, err := os.ReadFile("../.github/workflows/publish-gateway-image.yml")
	if err != nil {
		t.Fatal(err)
	}
	callerContent := string(caller)
	for _, required := range []string{
		"on:\n  push:\n    branches: [main]\n",
		"permissions: {}",
		"uses: pingcap-inc/nexuscrm-api-host/.github/workflows/trusted-publish-gateway-image.yml@c2cedc3d50ef39e79e93b9dd0d0f34c22592d1d6",
		"NEXUSCRM_CONTRACT_DEPLOY_KEY: ${{ secrets.NEXUSCRM_CONTRACT_DEPLOY_KEY }}",
	} {
		if !strings.Contains(callerContent, required) {
			t.Fatalf("active Gateway publisher missing %q", required)
		}
	}
	for _, forbidden := range []string{"run:", "actions/checkout@", "@refs/heads/", "secrets: inherit"} {
		if strings.Contains(callerContent, forbidden) {
			t.Fatalf("active Gateway caller gained mutable behavior %q", forbidden)
		}
	}
	workflow, err := os.ReadFile("../.github/workflows/trusted-publish-gateway-image.yml")
	if err != nil {
		t.Fatal(err)
	}
	content := string(workflow)
	triggerStart := strings.Index(content, "on:\n")
	permissionsStart := strings.Index(content, "\npermissions: {}\n")
	if triggerStart < 0 || permissionsStart <= triggerStart {
		t.Fatal("Gateway publication workflow must have explicit triggers and permissions")
	}
	if got, want := content[triggerStart:permissionsStart], "on:\n  workflow_call:\n    secrets:\n      NEXUSCRM_CONTRACT_DEPLOY_KEY:\n        required: true\n"; got != want {
		t.Fatalf("trusted Gateway publisher foundation trigger drifted:\n%s", got)
	}
	if count := strings.Count(content, "id-token: write"); count != 1 {
		t.Fatalf("exactly one source-free signer job may request GitHub OIDC, got %d grants", count)
	}
	validateStart := strings.Index(content, "\n  validate:\n")
	candidateStart := strings.Index(content, "\n  candidate:\n")
	signerStart := strings.Index(content, "\n  sign:\n")
	verifyNewStart := strings.Index(content, "\n  verify-new:\n")
	commitReleaseStart := strings.Index(content, "\n  commit-release:\n")
	verifyExistingStart := strings.Index(content, "\n  verify-existing:\n")
	if validateStart < 0 || candidateStart <= validateStart || signerStart <= candidateStart || verifyNewStart <= signerStart || commitReleaseStart <= verifyNewStart || verifyExistingStart <= commitReleaseStart {
		t.Fatal("Gateway publication must separate validation, candidate, signer, pre-publication verification, release commit, and existing-release verification jobs")
	}
	validate := content[validateStart:candidateStart]
	candidate := content[candidateStart:signerStart]
	signer := content[signerStart:verifyNewStart]
	verifyNew := content[verifyNewStart:commitReleaseStart]
	commitRelease := content[commitReleaseStart:verifyExistingStart]
	verifyExisting := content[verifyExistingStart:]
	for _, required := range []string{
		"permissions:\n      contents: read\n",
		"go test ./... -count=1",
		"go test -race ./... -count=1",
		"go vet ./...",
		"./scripts/check-gateway-boundary.sh",
		`test -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}"`,
		`test -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}"`,
	} {
		if !strings.Contains(validate, required) {
			t.Fatalf("validation job missing %q", required)
		}
	}
	for _, forbidden := range []string{"packages: write", "id-token: write", "docker/login-action@", "docker/build-push-action@", "cosign ", "aws "} {
		if strings.Contains(validate, forbidden) {
			t.Fatalf("validation job gained publication authority %q", forbidden)
		}
	}
	for _, required := range []string{
		"needs: validate",
		"permissions:\n      contents: read\n      packages: write\n",
		"outputs:\n      exists: ${{ steps.existing.outputs.exists }}",
		"image_digest: ${{ steps.identity.outputs.image_digest }}",
		"CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build",
		`test -z "$(git status --porcelain --untracked-files=no)"`,
		"github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl",
		"StrictHostKeyChecking=yes",
		"docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8",
		"tags: ${{ env.IMAGE_NAME }}:pending-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}",
		`test -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}"`,
		`test -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}"`,
	} {
		if !strings.Contains(candidate, required) {
			t.Fatalf("candidate job missing %q", required)
		}
	}
	for _, forbidden := range []string{
		"id-token: write",
		"ssh-keyscan",
		"go test ",
		"go vet ",
		"./scripts/",
		"configure-aws-credentials@",
		"aws kms",
		"cosign sign",
		"gateway-image-signing-policy.sh sign",
		"gateway-image-signing-policy.sh attest",
		"gateway-image-signing-policy.sh request-identity",
		"Commit verified Gateway source tag",
		"provenance_digest: ${{ steps.identity.outputs.provenance_digest }}",
		"sbom_digest: ${{ steps.identity.outputs.sbom_digest }}",
	} {
		if strings.Contains(candidate, forbidden) {
			t.Fatalf("candidate job must not have signing authority or commit release state: %q", forbidden)
		}
	}
	for _, required := range []string{
		"needs: candidate",
		"if: ${{ needs.candidate.outputs.exists != 'true' }}",
		"permissions:\n      contents: read\n      packages: write\n      id-token: write\n",
		"IMAGE_DIGEST: ${{ needs.candidate.outputs.image_digest }}",
		`source_tag="$IMAGE_NAME:pending-${GITHUB_SHA}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"`,
		`test "$source_digest" = "$IMAGE_DIGEST"`,
		`test "$revision" = "$GITHUB_SHA"`,
		"aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885",
		"Materialize and validate source-owned verifier facts as data",
		`.repository_visibility == "private"`,
		`.runner_environment == "github-hosted"`,
		`.oidc_max_token_lifetime_seconds == 600`,
		`.key_spec == "ECC_NIST_P256"`,
		`.key_usage == "SIGN_VERIFY"`,
		`.signing_algorithm == "ECDSA_SHA_256"`,
		`.publisher_state == "active"`,
		"Request GitHub identity and construct canonical provenance before AWS access",
		`.job_workflow_ref == $trusted_workflow_ref`,
		`.job_workflow_sha == $trusted_workflow_sha`,
		`test "$builder_id" = "https://github.com/$trusted_workflow_ref"`,
		"cosign attest --yes",
		"cosign sign --yes",
		"cosign verify --key",
		"cosign verify-attestation --key",
		"Owner-signed provenance:",
		`test -n "${ACTIONS_ID_TOKEN_REQUEST_URL:-}"`,
		`test -n "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}"`,
	} {
		if !strings.Contains(signer, required) {
			t.Fatalf("isolated signer job missing %q", required)
		}
	}
	// A signer has job-scoped OIDC and can exchange it for KMS without using the
	// official action. It must therefore never checkout, compile, interpret, or
	// execute repository code anywhere in the job, before or after exchange.
	for _, forbidden := range []string{
		"actions/checkout@",
		"actions/setup-go@",
		"go run ",
		"NEXUSCRM_CONTRACT_DEPLOY_KEY",
		"go mod download",
		"go test ",
		"go vet ",
		"CGO_ENABLED",
		"./scripts/",
		"./cmd/",
		"./internal/",
		"docker/build-push-action@",
		"setup-buildx-action@",
		"EXPECTED_PROVENANCE_DIGEST",
		"EXPECTED_SBOM_DIGEST",
		"Commit independently verified Gateway source tag",
		"docker buildx imagetools create --tag",
	} {
		if strings.Contains(signer, forbidden) {
			t.Fatalf("isolated signer job contains build/test/dependency execution %q", forbidden)
		}
	}
	for _, required := range []string{
		"needs: [candidate, sign]",
		"if: ${{ needs.candidate.outputs.exists != 'true' }}",
		"permissions:\n      contents: read\n      packages: read\n",
		"Checkout exact source revision without persisted credentials",
		"Verify complete release identity before publication",
		"gateway-image-signing-policy.sh verify-release",
		`test -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}"`,
		`test -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}"`,
	} {
		if !strings.Contains(verifyNew, required) {
			t.Fatalf("pre-publication verifier missing %q", required)
		}
	}
	for _, forbidden := range []string{"packages: write", "id-token: write", "configure-aws-credentials@", "aws kms", "cosign sign", "cosign attest", "imagetools create --tag"} {
		if strings.Contains(verifyNew, forbidden) {
			t.Fatalf("pre-publication verifier gained signing or release-commit authority %q", forbidden)
		}
	}
	for _, required := range []string{
		"needs: [candidate, verify-new]",
		"if: ${{ needs.candidate.outputs.exists != 'true' }}",
		"permissions:\n      packages: write\n",
		"Log in to GitHub Container Registry without source execution",
		"Commit independently verified Gateway source tag",
		"stable Gateway tag appeared before commit",
		"docker buildx imagetools create --tag \"$image_tag\"",
	} {
		if !strings.Contains(commitRelease, required) {
			t.Fatalf("release-commit job missing %q", required)
		}
	}
	for _, forbidden := range []string{
		"contents: read", "id-token: write", "actions/checkout@", "actions/setup-go@",
		"configure-aws-credentials@", "aws ", "cosign ", "./scripts/", "go run ", "go test ",
		"go vet ", "CGO_ENABLED", "NEXUSCRM_CONTRACT_DEPLOY_KEY",
	} {
		if strings.Contains(commitRelease, forbidden) {
			t.Fatalf("release-commit job gained source execution or signing authority %q", forbidden)
		}
	}
	for _, required := range []string{
		"group: publish-gateway-image-main",
		"cancel-in-progress: false",
		"actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5",
		"actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff",
		"ref: ${{ github.sha }}",
		"go-version: '1.25.0'",
		`test "$GITHUB_REF" = "refs/heads/main"`,
		`test "$(git rev-parse HEAD)" = "$GITHUB_SHA"`,
		"tags: ${{ env.IMAGE_NAME }}:pending-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}",
		"build-args: SOURCE_SHA=${{ github.sha }}",
		"labels: org.opencontainers.image.revision=${{ github.sha }}",
		"CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build",
		"platforms: linux/amd64",
		"NEXUSCRM_CONTRACT_DEPLOY_KEY",
		"Verify private credential cleanup",
		"go test ./... -count=1",
		"go test -race ./... -count=1",
		"go vet ./...",
		"./scripts/check-gateway-boundary.sh",
		"COSIGN_VERSION: v3.0.6",
		"COSIGN_LINUX_AMD64_SHA256: c956e5dfcac53d52bcf058360d579472f0c1d2d9b69f55209e256fe7783f4c74",
		"https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64",
		`printf '%s  %s\n' "$COSIGN_LINUX_AMD64_SHA256" "$cosign_path" | sha256sum --check`,
		"docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f",
		"docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9",
		"driver: docker-container",
		"aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885",
		"role-to-assume: ${{ env.AWS_ROLE_ARN }}",
		"aws-region: ${{ env.AWS_REGION }}",
		"aws sts get-caller-identity",
		`expected_caller_arn="arn:aws:sts::${expected_account}:assumed-role/${expected_role_name}/gateway-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"`,
		"aws kms describe-key",
		"cosign public-key --key \"awskms:///$KMS_KEY_ARN\"",
		`cmp "$exported_der" "$committed_der"`,
		"Public-key SHA-256:",
		"ECC_NIST_P256",
		"SIGN_VERIFY",
		"push: true",
		"provenance: mode=max",
		"sbom: true",
		"IMAGE_DIGEST: ${{ steps.publish.outputs.digest || steps.existing.outputs.digest }}",
		"IMAGE_DIGEST: ${{ needs.candidate.outputs.image_digest }}",
		`--data-urlencode "scope=repository:$repository:pull,push"`,
		`"https://ghcr.io/v2/$repository/manifests/$GITHUB_SHA"`,
		`if [ "$status" = 200 ]; then`,
		`elif [ "$status" = 404 ]; then`,
		`refusing to publish`,
		"if: ${{ needs.candidate.outputs.exists != 'true' }}",
		`test "$tag_digest" = "$IMAGE_DIGEST"`,
		`test "$revision" = "$GITHUB_SHA"`,
		`/slsa.dev\/provenance/`,
		`/spdx/`,
		"Provenance predicate digest:",
		"SBOM predicate digest:",
		"docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8",
		"verify-existing:",
		"verify-new:",
		"commit-release:",
		"Verify complete release identity before publication",
		"Reverify immutable existing release without signing authority",
		"gateway-image-signing-policy.sh verify-release",
		"docker buildx imagetools create --tag \"$image_tag\"",
	} {
		if !strings.Contains(content, required) {
			t.Fatalf("Gateway publication workflow missing %q", required)
		}
	}
	for _, forbidden := range []string{
		"pull_request:",
		"pull_request_target:",
		"workflow_dispatch:",
		"schedule:",
		":latest",
		"github.ref_name",
		"github.run_number",
		"go-version: '1.25.x'",
		"actions/attest@",
		"gh attestation verify",
		"create-storage-record",
		"sigstore/cosign-installer@",
		"cosign-release:",
		"vars.D1_SIGNING",
	} {
		if strings.Contains(content, forbidden) {
			t.Fatalf("Gateway publication workflow contains mutable or untrusted trigger/tag input %q", forbidden)
		}
	}
	if count := strings.Count(candidate, "if: ${{ steps.existing.outputs.exists != 'true' }}"); count != 2 {
		t.Fatalf("candidate overwrite guard must protect only build and pending publication, got %d guards", count)
	}
	candidateLogin := strings.Index(candidate, "- name: Log in to GitHub Container Registry")
	publish := strings.Index(candidate, "- name: Publish exact-main Gateway image")
	identity := strings.Index(candidate, "- name: Verify image, provenance, and SBOM identities")
	preflight := strings.Index(signer, "- name: Materialize and validate source-owned verifier facts as data")
	signerLogin := strings.Index(signer, "- name: Log in to GitHub Container Registry")
	independentIdentity := strings.Index(signer, "- name: Independently verify candidate identity")
	identityProof := strings.Index(signer, "- name: Request GitHub identity and construct canonical provenance before AWS access")
	oidc := strings.Index(signer, "- name: Exchange GitHub OIDC identity for owner signing role")
	sign := strings.Index(signer, "- name: Sign canonical provenance and exact image digest")
	verify := strings.Index(signer, "- name: Verify owner signature and exact build identity")
	if candidateLogin < 0 || publish <= candidateLogin || identity <= publish {
		t.Fatal("candidate job must build and inspect only a pending digest")
	}
	if preflight < 0 || signerLogin <= preflight || independentIdentity <= signerLogin || identityProof <= independentIdentity || oidc <= identityProof || sign <= oidc || verify <= sign {
		t.Fatal("source-free signer must resolve data, verify candidate, prepare identity, exchange authority, sign, then locally verify")
	}
	if count := strings.Count(signer[oidc:], "uses:"); count != 1 {
		t.Fatalf("only the pinned AWS credential exchange action may run after signing credentials exist, got %d actions", count)
	}
	for _, required := range []string{
		"if: ${{ needs.candidate.outputs.exists == 'true' }}",
		"permissions:\n      contents: read\n      packages: read\n",
		`test -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}"`,
		`test -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}"`,
	} {
		if !strings.Contains(verifyExisting, required) {
			t.Fatalf("existing-release verifier missing %q", required)
		}
	}
	for _, forbidden := range []string{"id-token: write", "configure-aws-credentials@", "aws kms", "cosign sign", "cosign attest"} {
		if strings.Contains(verifyExisting, forbidden) {
			t.Fatalf("existing-release verifier gained signing authority %q", forbidden)
		}
	}
	readme, err := os.ReadFile("../README.md")
	if err != nil {
		t.Fatal(err)
	}
	readmeWords := strings.Join(strings.Fields(string(readme)), " ")
	for _, required := range []string{
		"pending-*",
		"not a release identity or an L2 input",
		"first successfully committed exact-SHA tag",
		"Signer-authored annotations alone are never workflow proof",
		"custom audience contains that exact image digest",
		"each on a fresh runner, as hard capability boundaries",
		"Step ordering within one runner is not treated as credential isolation",
		"owner-signed provenance receipt",
	} {
		if !strings.Contains(readmeWords, required) {
			t.Fatalf("Gateway release contract lost candidate/authority distinction %q", required)
		}
	}
	fetch := strings.Index(candidate, "- name: Fetch exact private module pins")
	cleanup := strings.Index(candidate, "- name: Verify private credential cleanup")
	if fetch < 0 || cleanup <= fetch || candidateLogin <= cleanup {
		t.Fatal("builder dependency fetch, credential cleanup, and registry login must remain strictly ordered")
	}
}
