# Generated by Orbis CI/CD - Next.js (SSR / Full-Stack)
# Place in .github/workflows/orbis-ci.yml and add the repository secret ORBIS_INGEST_TOKEN.
# The tests run on GitHub's runners; every stage reports its real result to http://cicd.orbisai.ae.
name: "Orbis CI - Next.js (SSR / Full-Stack) Quality Gate"

on:
  push:
    branches: [ main, master, develop, "feature/**" ]
  pull_request:
    branches: [ main, master ]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

defaults:
  run:
    shell: bash

env:
  ORBIS_URL: http://cicd.orbisai.ae
  ORBIS_LIB: .orbis/orbis-ci-lib.sh
  ORBIS_PROJECT_TYPE: nextjs
  ORBIS_STAGES: '[{"name":"secret_scan","displayName":"Secret scan","type":"security"},{"name":"lint_check","displayName":"ESLint logic rules","type":"build"},{"name":"duplicate_code","displayName":"Duplicate-code detection","type":"build"},{"name":"dependency_integrity","displayName":"Dependency integrity","type":"build"},{"name":"license_compliance","displayName":"Licence compliance","type":"security"},{"name":"unit_test","displayName":"Unit tests","type":"test"},{"name":"server_actions_test","displayName":"Server Actions & API route tests","type":"test"},{"name":"integration_test","displayName":"Integration tests","type":"test"},{"name":"e2e_test","displayName":"Playwright E2E tests","type":"test"},{"name":"security_cve","displayName":"CVE audit","type":"security"},{"name":"build","displayName":"Next.js build","type":"build"}]'

jobs:
  orbis-quality-gate:
    # Stable name: GitHub branch rules require this exact check name (see scripts/orbis-protect-branch.sh)
    name: "Orbis Quality Gate"
    runs-on: ubuntu-latest
    timeout-minutes: 60
    env:
      ORBIS_INGEST_TOKEN: ${{ secrets.ORBIS_INGEST_TOKEN }}
    steps:
      - name: "Checkout code"
        uses: actions/checkout@v4

      - name: "Orbis - report run started"
        env:
          COMMIT_MSG: ${{ github.event.head_commit.message || github.event.pull_request.title }}
        run: |
          mkdir -p .orbis
          cat > "$ORBIS_LIB" <<'ORBIS_LIB_EOF'
          # Orbis CI/CD reporting helpers.
          # Embedded by lib/workflow-generator.js into every generated GitHub Actions workflow
          # and sourced by each stage. The tests run on the GitHub runner; this file only
          # reports the real outcome (and the log) to the Orbis dashboard.
          #
          # Required env: ORBIS_URL, ORBIS_STAGES (json), ORBIS_INGEST_TOKEN (repo secret)
          
          orbis_post() {
            local path="$1" payload="$2"
            [ -n "${ORBIS_INGEST_TOKEN:-}" ] || return 0
            curl -sS -L -m 20 -X POST "${ORBIS_URL}${path}" \
              -H "Content-Type: application/json" \
              -H "Authorization: Bearer ${ORBIS_INGEST_TOKEN}" \
              --data-binary "$payload" > /dev/null \
              || echo "::warning::Orbis ingest call to ${path} failed (the run is still validated by GitHub)"
            return 0
          }
          
          # Same as orbis_post but prints the server's JSON response (empty if the call fails).
          orbis_post_capture() {
            local path="$1" payload="$2"
            [ -n "${ORBIS_INGEST_TOKEN:-}" ] || return 0
            curl -sS -L -m 40 -X POST "${ORBIS_URL}${path}" \
              -H "Content-Type: application/json" \
              -H "Authorization: Bearer ${ORBIS_INGEST_TOKEN}" \
              --data-binary "$payload" 2>/dev/null || true
          }
          
          orbis_run_id() { echo "${GITHUB_RUN_ID}-${GITHUB_SHA}"; }
          
          # Fixed secret patterns, no external tool needed (works on Linux and Windows runners)
          orbis_secret_scan() {
            local pat='(AKIA[0-9A-Z]{16}|-----BEGIN (RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{60,}|xox[baprs]-[A-Za-z0-9-]{10,}|sk_live_[0-9a-zA-Z]{20,}|AIza[0-9A-Za-z_-]{35})'
            local hits
            hits=$(grep -rIlE "$pat" . \
              --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=.orbis --exclude-dir=reports \
              --exclude-dir=target --exclude-dir=vendor --exclude-dir=bin --exclude-dir=obj \
              --exclude=package-lock.json 2>/dev/null || true)
            if [ -n "$hits" ]; then
              echo "Possible secrets found in:"
              echo "$hits"
              return 1
            fi
            echo "No secrets detected."
          }
          export -f orbis_secret_scan
          
          orbis_start() {
            mkdir -p .orbis reports/tests reports/security
            date +%s > .orbis/start
            : > .orbis/failed
            if [ -z "${ORBIS_INGEST_TOKEN:-}" ]; then
              echo "::warning::ORBIS_INGEST_TOKEN secret is not set - results will NOT appear on the Orbis dashboard (tests still run and gate this workflow)."
            fi
            orbis_post /api/pipelines/start "$(jq -cn \
              --arg id "$(orbis_run_id)" \
              --arg repo "$GITHUB_REPOSITORY" \
              --arg branch "$GITHUB_REF_NAME" \
              --arg commit "$GITHUB_SHA" \
              --arg author "$GITHUB_ACTOR" \
              --arg title "${COMMIT_MSG:-}" \
              --arg type "${ORBIS_PROJECT_TYPE:-}" \
              --arg runUrl "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
              --argjson stages "$ORBIS_STAGES" \
              '{externalRunId:$id, repo:$repo, projectId:$repo, projectType:$type, branch:$branch, commitHash:$commit,
                author:$author, title:($title|split("\n")[0]), runUrl:$runUrl, stages:$stages}')"
          }
          
          # orbis_stage <stageName> <displayName> <command>
          # Runs the command for real, keeps its full log as an artifact, reports the true result.
          orbis_stage() {
            local name="$1" display="$2" cmd="$3"
            local log="reports/tests/${name}.log" t0 rc status excerpt
            mkdir -p reports/tests
            t0=$(date +%s)
            orbis_post /api/pipelines/stage-result "$(jq -cn --arg id "$(orbis_run_id)" --arg n "$name" --arg d "$display" \
              '{externalRunId:$id, stageName:$n, displayName:$d, status:"running"}')"
          
            set +e
            # the project's own test code must never see the ingest token
            env -u ORBIS_INGEST_TOKEN bash -eo pipefail -c "$cmd" 2>&1 | tee "$log"
            rc=${PIPESTATUS[0]}
            set -e
          
            status=success
            if [ "$rc" -ne 0 ]; then
              status=failed
              echo "$name" >> .orbis/failed
              echo "::error::Stage '$display' failed with exit code $rc (log: reports/tests/${name}.log)"
            fi
          
            excerpt=$(tail -c 3000 "$log" | sed -E 's/\x1b\[[0-9;]*[A-Za-z]//g')
            local payload resp text
            payload="$(jq -cn --arg id "$(orbis_run_id)" --arg n "$name" --arg d "$display" \
              --arg s "$status" --arg l "$excerpt" --argjson ms "$(( ($(date +%s) - t0) * 1000 ))" \
              '{externalRunId:$id, stageName:$n, displayName:$d, status:$s, logExcerpt:$l, durationMs:$ms}')"
          
            if [ "$status" = "failed" ]; then
              # The server explains the failure (pattern rules + optional AI). Show it right here in the Actions log.
              resp=$(orbis_post_capture /api/pipelines/stage-result "$payload")
              text=$(printf '%s' "$resp" | jq -r '.analysis.text // empty' 2>/dev/null || true)
              if [ -n "$text" ]; then
                echo "$text"
                { echo "### Orbis suggestions - $display"; echo '```'; echo "$text"; echo '```'; } >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
              fi
            else
              orbis_post /api/pipelines/stage-result "$payload"
            fi
            return "$rc"
          }
          
          # Gather every report the common tools leave behind into reports/ (uploaded as an artifact)
          orbis_collect() {
            mkdir -p reports/tests reports/security reports/coverage
            npm audit --json > reports/security/npm-audit.json 2>/dev/null || true
            local d
            for d in coverage test-results playwright-report TestResults target/surefire-reports target/site/jacoco; do
              if [ -d "$d" ]; then cp -r "$d" "reports/tests/$(echo "$d" | tr '/' '_')"; fi
            done
            for d in target/spotbugsXml.xml target/dependency-check-report.html target/dependency-check-report.json; do
              if [ -f "$d" ]; then cp "$d" reports/security/; fi
            done
            find . -maxdepth 4 \( -name 'junit*.xml' -o -name '*.trx' -o -name 'TEST-*.xml' \) \
              -not -path './node_modules/*' -not -path './reports/*' -exec cp {} reports/tests/ \; 2>/dev/null || true
            {
              echo "## Orbis artifact testing"
              echo ""
              echo "| File | Size (bytes) |"
              echo "|---|---|"
              find reports -type f | while read -r f; do echo "| $f | $(wc -c < "$f" | tr -d ' ') |"; done
            } >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
          }
          
          # Final gate: verifies the artifacts, reports the real result, fails the job if anything failed
          orbis_finish() {
            local failed count status t0 ms
            failed=$(tr '\n' ' ' < .orbis/failed 2>/dev/null | sed 's/ *$//')
            count=$(find reports/tests -type f 2>/dev/null | wc -l | tr -d ' ')
            if [ "${count:-0}" -eq 0 ]; then
              failed="${failed:+$failed }no-test-artifacts"
              echo "::error::No test artifacts were produced (expected logs in reports/tests/)."
            fi
            if [ "${JOB_STATUS:-success}" != "success" ] && [ -z "$failed" ]; then
              failed="job-${JOB_STATUS}"
            fi
            status=success
            [ -z "$failed" ] || status=failed
          
            t0=$(cat .orbis/start 2>/dev/null || date +%s)
            ms=$(( ($(date +%s) - t0) * 1000 ))
            orbis_post /api/pipelines/results "$(jq -cn \
              --arg id "$(orbis_run_id)" --arg repo "$GITHUB_REPOSITORY" --arg branch "$GITHUB_REF_NAME" \
              --arg commit "$GITHUB_SHA" --arg author "$GITHUB_ACTOR" --arg title "${COMMIT_MSG:-}" \
              --arg type "${ORBIS_PROJECT_TYPE:-}" --arg status "$status" --arg reason "$failed" \
              --arg runUrl "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
              --arg art "test-artifacts-${GITHUB_RUN_NUMBER}" --argjson ms "$ms" \
              '{externalRunId:$id, projectId:$repo, projectType:$type, repo:$repo, branch:$branch, commitHash:$commit,
                author:$author, title:($title|split("\n")[0]), status:$status, failureReason:$reason, durationMs:$ms,
                runUrl:$runUrl, artifacts:[{name:$art, url:$runUrl}]}')"
          
            if [ "$status" = "failed" ]; then
              echo "::error::Orbis quality gate FAILED. Failing: $failed"
              return 1
            fi
            echo "Orbis quality gate passed - every stage succeeded and test artifacts are uploaded."
          }
          ORBIS_LIB_EOF
          source "$ORBIS_LIB"
          orbis_start

      - name: "Setup 1"
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: "npm"

      - name: "Install dependencies"
        run: |
          npm ci || npm install

      - name: "Stage 1 - Secret scan"
        id: stage_1
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "secret_scan" "Secret scan" 'orbis_secret_scan'

      - name: "Stage 2 - ESLint logic rules"
        id: stage_2
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "lint_check" "ESLint logic rules" 'npm run lint'

      - name: "Stage 3 - Duplicate-code detection"
        id: stage_3
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "duplicate_code" "Duplicate-code detection" 'npx --yes jscpd . --min-lines 5 --min-tokens 50 --threshold 3 --reporters console,html --output reports/jscpd --gitignore --ignore "**/node_modules/**,**/dist/**,**/build/**,**/coverage/**,**/.next/**,**/target/**,**/vendor/**,**/bin/**,**/obj/**"'

      - name: "Stage 4 - Dependency integrity"
        id: stage_4
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "dependency_integrity" "Dependency integrity" 'npm ci --ignore-scripts --dry-run'

      - name: "Stage 5 - Licence compliance"
        id: stage_5
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "license_compliance" "Licence compliance" 'npx --yes license-checker --production --summary --failOn "GPL-3.0;AGPL-3.0"'

      - name: "Stage 6 - Unit tests"
        id: stage_6
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "unit_test" "Unit tests" 'npm run test:unit'

      - name: "Stage 7 - Server Actions & API route tests"
        id: stage_7
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "server_actions_test" "Server Actions & API route tests" 'npm run test:api'

      - name: "Stage 8 - Integration tests"
        id: stage_8
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "integration_test" "Integration tests" 'npm run test:integration'

      - name: "Stage 9 - Playwright E2E tests"
        id: stage_9
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "e2e_test" "Playwright E2E tests" 'npx --yes playwright install --with-deps chromium && npm run test:e2e'

      - name: "Stage 10 - CVE audit"
        id: stage_10
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "security_cve" "CVE audit" 'npm audit --audit-level=high'

      - name: "Stage 11 - Next.js build"
        id: stage_11
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "build" "Next.js build" 'npm run build'

      - name: "Collect test artifacts"
        if: always()
        run: |
          source "$ORBIS_LIB"
          orbis_collect

      - name: "Upload test artifacts"
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: "test-artifacts-${{ github.run_number }}"
          path: "reports/"
          retention-days: 14
          if-no-files-found: "warn"

      - name: "Upload build output"
        if: success()
        uses: actions/upload-artifact@v4
        with:
          name: "build-output"
          path: |
            .next/
            !.next/cache/**
          retention-days: 14
          if-no-files-found: "ignore"

      - name: "Orbis - verify artifacts and report final result"
        if: always()
        env:
          JOB_STATUS: ${{ job.status }}
          COMMIT_MSG: ${{ github.event.head_commit.message || github.event.pull_request.title }}
        run: |
          source "$ORBIS_LIB"
          orbis_finish
