#!/usr/bin/env bash
set -euo pipefail

fail() { echo "serviceification candidate producer: $*" >&2; exit 1; }

: "${PHASE:?}"
: "${ACTION:?}"
: "${PUBLISHER_ROOT:?}"
: "${SOURCE_SHA:?}"
: "${RUN_ID:?}"
: "${RUN_ATTEMPT:?}"
: "${OUTPUT_DIR:?}"
: "${CALLER_WORKFLOW_REF:?}"
: "${CALLER_WORKFLOW_SHA:?}"
: "${PRODUCER_WORKFLOW_REF:?}"
: "${PRODUCER_WORKFLOW_SHA:?}"
: "${GITHUB_OUTPUT:?}"

[[ "$PHASE" == frontend || "$PHASE" == resolved_bom ]] || fail "invalid phase"
[[ "$ACTION" == prepare || "$ACTION" == publish ]] || fail "invalid action"
[[ "$SOURCE_SHA" =~ ^[0-9a-f]{40}$ ]] || fail "source SHA must be exact"
[[ "$RUN_ID" =~ ^[1-9][0-9]*$ && "$RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]] || fail "run identity must be exact"
caller_prefix=pingcap-inc/nexuscrm-pingcap-pack/.github/workflows/run-serviceification-test-stack-candidate.yml@
producer_prefix=pingcap-inc/nexuscrm-pingcap-pack/.github/workflows/produce-serviceification-test-stack-candidate.yml@
source_ref=${CALLER_WORKFLOW_REF#"$caller_prefix"}
[[ "$CALLER_WORKFLOW_REF" == "$caller_prefix$source_ref" && "$source_ref" =~ ^refs/heads/.+ ]] \
  || fail "candidate caller does not identify an exact repository branch"
[[ "$PRODUCER_WORKFLOW_REF" == "$producer_prefix$source_ref" ]] \
  || fail "candidate producer does not identify the same repository branch"
[[ "$CALLER_WORKFLOW_SHA" == "$SOURCE_SHA" && "$PRODUCER_WORKFLOW_SHA" == "$SOURCE_SHA" ]] \
  || fail "caller, producer and source commits disagree"
[[ "$(git -C "$PUBLISHER_ROOT" rev-parse HEAD)" == "$SOURCE_SHA" ]] || fail "publisher checkout is not the requested source"

: "${DESIRED_BOM_PATH:=release/serviceification-d3-bom.json}"
case "$DESIRED_BOM_PATH" in
  release/serviceification-d3-bom.json) ;;
  *) fail "desired BOM path is not an authorized candidate profile" ;;
esac
bom="$PUBLISHER_ROOT/$DESIRED_BOM_PATH"
[[ -f "$bom" ]] || fail "desired BOM is missing"
python3 "$PUBLISHER_ROOT/scripts/validate_serviceification_bom.py" --bom "$bom" --require-desired
mkdir -p "$OUTPUT_DIR"

verify_checkout() {
  local root="$1" expected="$2" label="$3"
  [[ "$(git -C "$root" rev-parse --is-inside-work-tree 2>/dev/null)" == true ]] \
    || fail "$label checkout is missing"
  [[ "$(git -C "$root" rev-parse HEAD)" == "$expected" ]] || fail "$label checkout disagrees with desired BOM"
}

verify_l2_metadata() {
  local expected_tree l2_path actual_tree
  expected_tree="$(jq -er '.l2_metadata.tree_sha256' "$bom")"
  l2_path="$(jq -er '.l2_metadata.path' "$bom")"
  actual_tree="$(PYTHONPATH="$PUBLISHER_ROOT/scripts" python3 - "$L2_INPUT_ROOT/$l2_path" <<'PY'
import sys
from pathlib import Path
from lib.final_pack_contract import metadata_tree_sha256
print(metadata_tree_sha256(Path(sys.argv[1])))
PY
)"
  [[ "$actual_tree" == "$expected_tree" ]] || fail "L2 metadata tree disagrees with desired BOM"
}

prepare_frontend() {
  : "${FRONTEND_RUNTIME_ROOT:?}" "${FRONTEND_APP_PACKS_ROOT:?}" "${L2_INPUT_ROOT:?}"
  runtime_commit="$(jq -er '.frontend.nexuscrm_commit' "$bom")"
  app_packs_commit="$(jq -er '.frontend.app_packs_commit' "$bom")"
  l2_commit="$(jq -er '.frontend.l2_commit' "$bom")"
  verify_checkout "$FRONTEND_RUNTIME_ROOT" "$runtime_commit" frontend-platform
  verify_checkout "$FRONTEND_APP_PACKS_ROOT" "$app_packs_commit" frontend-app-packs
  verify_checkout "$L2_INPUT_ROOT" "$l2_commit" frontend-L2-overlay

  materialized="$OUTPUT_DIR/frontend-app-packs-source"
  materialize_result="$(python3 "$PUBLISHER_ROOT/scripts/materialize_frontend_app_packs_source.py" \
    --downstream-root "$L2_INPUT_ROOT" --official-app-packs-root "$FRONTEND_APP_PACKS_ROOT" --target-root "$materialized")"
  materialized_root="$(jq -er '.root' <<<"$materialize_result")"
  materialized_ref="$(jq -er '.ref' <<<"$materialize_result")"
  git -C "$materialized_root" cat-file -e "$materialized_ref^{commit}"

  frontend_root="$FRONTEND_RUNTIME_ROOT/frontend"
  (
    cd "$frontend_root"
    unset REACT_APP_BACKEND_URL REACT_APP_FRONTEND_URL API_BASE_URL FRONTEND_URL
    NEXUSCRM_FRONTEND_APP_PACKS_ROOT="$materialized_root" \
      NEXUSCRM_FRONTEND_APP_PACKS_REF="$materialized_ref" \
      npm ci --include=dev
    NEXUSCRM_FRONTEND_APP_PACKS_ROOT="$materialized_root" \
      NEXUSCRM_FRONTEND_APP_PACKS_REF="$materialized_ref" \
      npm run build
  )
  [[ -f "$frontend_root/dist/index.html" && -f "$frontend_root/dist/version.json" ]] || fail "frontend build is incomplete"
  if grep -aERn '127\.0\.0\.1:(3001|5173|3198)|localhost:(3001|5173|3198)' "$frontend_root/dist"; then
    fail "frontend bundle contains an environment build host"
  fi

  context="$OUTPUT_DIR/frontend-context"
  mkdir -p "$context"
  cp -R "$frontend_root/dist" "$context/frontend-dist"
  PYTHONPATH="$PUBLISHER_ROOT/scripts" python3 - "$bom" "$context/source-tuple.json" <<'PY'
import sys
from pathlib import Path
from validate_serviceification_bom import canonical_json_bytes, frontend_source_tuple, load_bom
bom = load_bom(Path(sys.argv[1]))
Path(sys.argv[2]).write_bytes(canonical_json_bytes(frontend_source_tuple(bom["frontend"])))
PY
  binding="sha256:$(sha256sum "$context/source-tuple.json" | awk '{print $1}')"
  build_date="$(git -C "$L2_INPUT_ROOT" show -s --format=%cI "$l2_commit")"
  jq -n --arg version "$binding" --arg buildDate "$build_date" '{version:$version,buildDate:$buildDate}' \
    > "$context/frontend-dist/version.json"
  source_epoch="$(git -C "$L2_INPUT_ROOT" show -s --format=%ct "$l2_commit")"
  python3 - "$context" "$source_epoch" <<'PY'
import os
import sys
from pathlib import Path

root = Path(sys.argv[1])
epoch = int(sys.argv[2])
for path in sorted(root.rglob("*"), reverse=True):
    os.utime(path, (epoch, epoch), follow_symlinks=False)
os.utime(root, (epoch, epoch), follow_symlinks=False)
PY

  printf 'binding=%s\n' "$binding" >> "$GITHUB_OUTPUT"
}

publish_frontend() {
  : "${PREPARED_BINDING:?}"
  [[ "$PREPARED_BINDING" =~ ^sha256:[0-9a-f]{64}$ ]] || fail "prepared frontend binding is malformed"
  context="$OUTPUT_DIR/frontend-context"
  [[ -f "$context/frontend-dist/index.html" && -f "$context/source-tuple.json" ]] \
    || fail "prepared frontend context is missing"
  [[ "sha256:$(sha256sum "$context/source-tuple.json" | awk '{print $1}')" == "$PREPARED_BINDING" ]] \
    || fail "prepared frontend binding changed"
  image="$(jq -er '.frontend.oci_repository' "$bom")"
  [[ "$image" == ghcr.io/pingcap-inc/nexuscrm-frontend-assets ]] \
    || fail "frontend candidate repository is not the fixed L2 owner target"
  metadata="$OUTPUT_DIR/frontend-build-metadata.json"
  pending="$image:pending-$SOURCE_SHA-$RUN_ID-$RUN_ATTEMPT-frontend"
  docker buildx build --push --platform linux/amd64 --provenance=false --sbom=true \
    --build-arg "SOURCE_SHA=$SOURCE_SHA" --build-arg "SOURCE_TUPLE_SHA256=$PREPARED_BINDING" \
    --tag "$pending" --metadata-file "$metadata" \
    -f "$PUBLISHER_ROOT/infra/serviceification-frontend.Dockerfile" "$context"
  digest="$(jq -er '."containerimage.digest"' "$metadata")"
  [[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || fail "Buildx returned no frontend digest"
  docker pull --quiet --platform linux/amd64 "$image@$digest" >/dev/null
  [[ "$(docker image inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}' "$image@$digest")" == "$SOURCE_SHA" ]]
  [[ "$(docker image inspect --format '{{ index .Config.Labels "io.nexuscrm.frontend.source-tuple-sha256" }}' "$image@$digest")" == "$PREPARED_BINDING" ]]

  receipt="$OUTPUT_DIR/frontend-candidate.json"
  PYTHONPATH="$PUBLISHER_ROOT/scripts" python3 "$PUBLISHER_ROOT/scripts/create_serviceification_candidate_receipt.py" frontend \
    --desired-bom "$bom" --publisher-commit "$SOURCE_SHA" --producer-root "$PUBLISHER_ROOT" \
    --caller-workflow-ref "$CALLER_WORKFLOW_REF" --caller-workflow-sha "$CALLER_WORKFLOW_SHA" \
    --producer-workflow-ref "$PRODUCER_WORKFLOW_REF" --producer-workflow-sha "$PRODUCER_WORKFLOW_SHA" \
    --run-id "$RUN_ID" --run-attempt "$RUN_ATTEMPT" \
    --digest "$digest" --output "$receipt"
  printf 'digest=%s\nbinding=%s\nreceipt=%s\n' "$digest" "$PREPARED_BINDING" "$receipt" >> "$GITHUB_OUTPUT"
}

prepare_resolved_bom() {
  : "${RUNTIME_ROOT:?}" "${APP_SERVICE_ROOT:?}" "${L2_INPUT_ROOT:?}" \
    "${APP_RELEASE_ROOT:?}" "${FRONTEND_OWNER_ROOT:?}"
  : "${FRONTEND_SOURCE_COMMIT:?}" "${FRONTEND_DIGEST:?}" "${FRONTEND_BINDING:?}"
  [[ "$FRONTEND_SOURCE_COMMIT" =~ ^[0-9a-f]{40}$ && \
     "$FRONTEND_DIGEST" =~ ^sha256:[0-9a-f]{64}$ && "$FRONTEND_BINDING" =~ ^sha256:[0-9a-f]{64}$ ]] \
    || fail "signed frontend identity is malformed"
  [[ "$(git -C "$RUNTIME_ROOT" rev-parse --is-inside-work-tree 2>/dev/null)" == true ]] \
    || fail "L0-Engine owner checkout is missing"
  verify_checkout "$APP_SERVICE_ROOT" "$(jq -er '.services.sfcrm_app_service.source_commit' "$bom")" AppService
  verify_checkout "$APP_RELEASE_ROOT" "$(jq -er '.app_installation.artifact.commit' "$bom")" AppRelease
  verify_checkout "$FRONTEND_OWNER_ROOT" "$FRONTEND_SOURCE_COMMIT" frontend-owner
  verify_l2_metadata

  # L0 does not select an L1 source. The explicit AppRelease is both the signed
  # L1 artifact and the source used to assemble its application-aware tool.
  official_app_packs_root="$APP_RELEASE_ROOT"

  receipts="$OUTPUT_DIR/verification-receipts"
  evidence="$OUTPUT_DIR/verification-evidence"
  mkdir -p "$receipts" "$evidence"
  : "${L0_ENGINE_DIGEST:?}"
  [[ "$L0_ENGINE_DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]] \
    || fail "resolved L0 Engine digest is malformed"
  "$PUBLISHER_ROOT/scripts/verify_serviceification_oci_publication.sh" \
    --owner-root "$RUNTIME_ROOT" --source-repository pingcap-inc/NexusCRM \
    --release-version "$(jq -er '.l0_engine.version' "$bom")" \
    --oci-repository "$(jq -er '.l0_engine.oci_repository' "$bom")" \
    --digest "$L0_ENGINE_DIGEST" --role l0_engine \
    --output "$receipts/l0_engine.json" \
    --evidence-output "$evidence/l0_engine.evidence.json"
  "$PUBLISHER_ROOT/scripts/verify_serviceification_oci_publication.sh" \
    --owner-root "$APP_SERVICE_ROOT" --source-repository pingcap-inc/nexuscrm-app-packs \
    --source-commit "$(jq -er '.services.sfcrm_app_service.source_commit' "$bom")" \
    --oci-repository "$(jq -er '.services.sfcrm_app_service.oci_repository' "$bom")" --role sfcrm_app_service --output "$receipts/sfcrm_app_service.json" \
    --evidence-output "$evidence/sfcrm_app_service.evidence.json"
  if jq -e '.services.mcp_worker != null' "$bom" >/dev/null; then
    : "${MCP_OWNER_ROOT:?}"
    mcp_commit="$(jq -er '.services.mcp_worker.source_commit' "$bom")"
    verify_checkout "$MCP_OWNER_ROOT" "$mcp_commit" MCP
    "$PUBLISHER_ROOT/scripts/verify_serviceification_oci_publication.sh" \
      --owner-root "$MCP_OWNER_ROOT" --source-repository pingcap-inc/nexuscrm-app-packs \
      --source-commit "$mcp_commit" --oci-repository ghcr.io/pingcap-inc/nexuscrm-mcp-worker \
      --role mcp_worker --digest "$(jq -er '.services.mcp_worker.digest' "$bom")" \
      --output "$receipts/mcp_worker.json" \
      --evidence-output "$evidence/mcp_worker.evidence.json"
  fi
  "$PUBLISHER_ROOT/scripts/verify_serviceification_oci_publication.sh" \
    --owner-root "$FRONTEND_OWNER_ROOT" --source-repository pingcap-inc/nexuscrm-pingcap-pack \
    --source-commit "$FRONTEND_SOURCE_COMMIT" --oci-repository "$(jq -er '.frontend.oci_repository' "$bom")" \
    --digest "$FRONTEND_DIGEST" --role frontend --binding "$FRONTEND_BINDING" --output "$receipts/frontend.json" \
    --evidence-output "$evidence/frontend.evidence.json"

  resolved="$OUTPUT_DIR/resolved-bom.json"
  PYTHONPATH="$PUBLISHER_ROOT/scripts" python3 "$PUBLISHER_ROOT/scripts/resolve_serviceification_bom.py" \
    --desired-bom "$bom" --receipts-dir "$receipts" \
    --frontend-publisher-commit "$FRONTEND_SOURCE_COMMIT" --output "$resolved"
  python3 "$PUBLISHER_ROOT/scripts/validate_serviceification_bom.py" \
    --bom "$resolved" --app-packs-root "$APP_RELEASE_ROOT" --require-deployable
  desired_digest="$(jq -er '.desired_bom_sha256' "$resolved")"

  context="$OUTPUT_DIR/resolved-bom-context"
  mkdir -p "$context"
  cp "$resolved" "$context/resolved-bom.json"
  # Compile the complete L2 Approval closure from the exact candidate inputs.
    # Stopped snapshots preserve the old runtime's ProcessDefinition/Node/Action
    # execution truth; directly authored Account metadata is lowered by the same
    # L1-owned compiler. Runtime consumes only the resulting neutral bundle.
    approval_source_digest="$(jq -er '.l2_metadata.tree_sha256' "$resolved")"
    approval_work="$OUTPUT_DIR/approval-compiler-work"
    mkdir -p "$approval_work"
    rm -f "$approval_work/go.work" "$approval_work/go.work.sum"
    GOWORK="$approval_work/go.work" go work init \
      "$official_app_packs_root/dialects/dialect-sf-crm" \
      "$official_app_packs_root/dialects/dialect-sf-crm-soql" \
      "$official_app_packs_root/sfcrm/wire"
    GOWORK="$approval_work/go.work" go work edit \
      -replace "github.com/nexuscrm/shared=$RUNTIME_ROOT/shared" \
      -replace "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/querycodec=$official_app_packs_root/sfcrm/querycodec"
    (
      cd "$official_app_packs_root"
      GIT_CONFIG_COUNT=1 \
      GIT_CONFIG_KEY_0="url.https://x-access-token:${CROSS_REPO_READ_TOKEN:?}@github.com/.insteadOf" \
      GIT_CONFIG_VALUE_0=https://github.com/ \
      GOPRIVATE=github.com/pingcap-inc/* \
      GONOSUMDB=github.com/pingcap-inc/* \
      GOWORK="$approval_work/go.work" go run ./dialects/dialect-sf-crm/cmd/approval-publication \
        -metadata-source-digest "$approval_source_digest" \
        -input "$L2_INPUT_ROOT/deployments/offline/runtime/seeds/credits-approvals.seed.json" \
        -metadata-input "Account/Account_Approval_APAC=$L2_INPUT_ROOT/approvalProcesses/Account.Account_Approval_APAC.approvalProcess" \
        -output "$context/approval-publication.json"
    )
  printf 'catalog_source=%s\n' \
    "$(jq -er '.l0_engine.release.catalog_reconcile.source_sha' "$resolved")" \
    >> "$GITHUB_OUTPUT"
  printf 'binding=%s\n' "$desired_digest" >> "$GITHUB_OUTPUT"
}

publish_resolved_bom() {
  : "${PREPARED_BINDING:?}" "${FRONTEND_SOURCE_COMMIT:?}" "${FRONTEND_DIGEST:?}" "${FRONTEND_BINDING:?}"
  [[ "$PREPARED_BINDING" =~ ^sha256:[0-9a-f]{64}$ ]] || fail "prepared BOM binding is malformed"
  context="$OUTPUT_DIR/resolved-bom-context"
  resolved="$OUTPUT_DIR/resolved-bom.json"
  receipts="$OUTPUT_DIR/verification-receipts"
  evidence="$OUTPUT_DIR/verification-evidence"
  [[ -f "$context/resolved-bom.json" && -f "$resolved" ]] || fail "prepared resolved BOM is missing"
  [[ -f "$context/deployment-tools/manifest.json" ]] || fail "prepared deployment tools are missing"
  cmp "$resolved" "$context/resolved-bom.json" || fail "prepared resolved BOM context changed"
  [[ "$(jq -er '.desired_bom_sha256' "$resolved")" == "$PREPARED_BINDING" ]] \
    || fail "prepared resolved BOM binding changed"
  image=ghcr.io/pingcap-inc/nexuscrm-serviceification-bom
  metadata="$OUTPUT_DIR/resolved-bom-build-metadata.json"
  pending="$image:pending-$SOURCE_SHA-$RUN_ID-$RUN_ATTEMPT-resolved-bom"
  docker buildx build --push --platform linux/amd64 --provenance=false --sbom=true \
    --build-arg "SOURCE_SHA=$SOURCE_SHA" --build-arg "DESIRED_BOM_SHA256=$PREPARED_BINDING" \
    --tag "$pending" --metadata-file "$metadata" \
    -f "$PUBLISHER_ROOT/infra/serviceification-resolved-bom.Dockerfile" "$context"
  digest="$(jq -er '."containerimage.digest"' "$metadata")"
  [[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || fail "Buildx returned no resolved-BOM digest"
  docker pull --quiet --platform linux/amd64 "$image@$digest" >/dev/null
  [[ "$(docker image inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}' "$image@$digest")" == "$SOURCE_SHA" ]]
  [[ "$(docker image inspect --format '{{ index .Config.Labels "io.nexuscrm.serviceification.desired-bom-sha256" }}' "$image@$digest")" == "$PREPARED_BINDING" ]]

  receipt="$OUTPUT_DIR/resolved-bom-candidate.json"
  PYTHONPATH="$PUBLISHER_ROOT/scripts" python3 "$PUBLISHER_ROOT/scripts/create_serviceification_candidate_receipt.py" resolved-bom \
    --desired-bom "$bom" --publisher-commit "$SOURCE_SHA" --producer-root "$PUBLISHER_ROOT" \
    --caller-workflow-ref "$CALLER_WORKFLOW_REF" --caller-workflow-sha "$CALLER_WORKFLOW_SHA" \
    --producer-workflow-ref "$PRODUCER_WORKFLOW_REF" --producer-workflow-sha "$PRODUCER_WORKFLOW_SHA" \
    --run-id "$RUN_ID" --run-attempt "$RUN_ATTEMPT" \
    --digest "$digest" --resolved-bom "$resolved" --receipts-dir "$receipts" --evidence-dir "$evidence" \
    --frontend-source-commit "$FRONTEND_SOURCE_COMMIT" \
    --frontend-digest "$FRONTEND_DIGEST" --frontend-binding "$FRONTEND_BINDING" --output "$receipt"
  printf 'digest=%s\nbinding=%s\nreceipt=%s\n' "$digest" "$PREPARED_BINDING" "$receipt" >> "$GITHUB_OUTPUT"
}

case "$PHASE:$ACTION" in
  frontend:prepare) prepare_frontend ;;
  frontend:publish) publish_frontend ;;
  resolved_bom:prepare) prepare_resolved_bom ;;
  resolved_bom:publish) publish_resolved_bom ;;
esac
