# Generated by Orbis CI/CD - .NET (EcommerceSupportApp)
# 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 - .NET (EcommerceSupportApp) 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: dotnet-mvc
  ORBIS_STAGES: '[{"name":"secret_scan","displayName":"Secret scan","type":"security"},{"name":"nuget_restore","displayName":"NuGet restore","type":"build"},{"name":"webconfig_audit","displayName":"Web.config security audit","type":"security"},{"name":"msbuild_release","displayName":"MSBuild 17 Release compile","type":"build"},{"name":"roslyn_analysis","displayName":"Roslyn analysis","type":"build"},{"name":"unit_tests","displayName":"Unit & controller tests","type":"test"},{"name":"vulnerable_packages","displayName":"Vulnerable-package audit","type":"security"},{"name":"webdeploy_package","displayName":"IIS Web Deploy package","type":"deploy"}]'

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: windows-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: microsoft/setup-msbuild@v2

      - name: "Setup 2"
        uses: NuGet/setup-nuget@v2

      - name: "Setup 3"
        uses: microsoft/setup-vstest@v3

      - 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 - NuGet restore"
        id: stage_2
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "nuget_restore" "NuGet restore" 'sln=$(ls *.sln 2>/dev/null | head -n1); test -n "$sln" || { echo "No .sln file found in the repository root"; exit 1; }; nuget restore "$sln"'

      - name: "Stage 3 - Web.config security audit"
        id: stage_3
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "webconfig_audit" "Web.config security audit" '! grep -rIn --include="Web*.config" -E "debug=\"true\"|customErrors mode=\"Off\"" . --exclude-dir=obj --exclude-dir=bin'

      - name: "Stage 4 - MSBuild 17 Release compile"
        id: stage_4
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "msbuild_release" "MSBuild 17 Release compile" 'sln=$(ls *.sln 2>/dev/null | head -n1); test -n "$sln" || { echo "No .sln file found in the repository root"; exit 1; }; msbuild "$sln" /m /p:Configuration=Release /p:Platform="Any CPU"'

      - name: "Stage 5 - Roslyn analysis"
        id: stage_5
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "roslyn_analysis" "Roslyn analysis" 'sln=$(ls *.sln 2>/dev/null | head -n1); test -n "$sln" || { echo "No .sln file found in the repository root"; exit 1; }; msbuild "$sln" /m /t:Rebuild /p:Configuration=Release /p:RunAnalyzers=true /p:EnforceCodeStyleInBuild=false'

      - name: "Stage 6 - Unit & controller tests"
        id: stage_6
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "unit_tests" "Unit & controller tests" 'dlls=$(find . -path "*/bin/Release/*" -name "*Test*.dll" -not -path "*/obj/*"); test -n "$dlls" || { echo "No test assemblies found (expected *Test*.dll in bin/Release)"; exit 1; }; vstest.console.exe $dlls /Logger:trx /ResultsDirectory:reports/tests'

      - name: "Stage 7 - Vulnerable-package audit"
        id: stage_7
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "vulnerable_packages" "Vulnerable-package audit" 'sln=$(ls *.sln 2>/dev/null | head -n1); test -n "$sln" || { echo "No .sln file found in the repository root"; exit 1; }; out=$(dotnet list "$sln" package --vulnerable --include-transitive 2>&1); echo "$out"; ! echo "$out" | grep -q "has the following vulnerable packages"'

      - name: "Stage 8 - IIS Web Deploy package"
        id: stage_8
        continue-on-error: true
        run: |
          source "$ORBIS_LIB"
          orbis_stage "webdeploy_package" "IIS Web Deploy package" 'sln=$(ls *.sln 2>/dev/null | head -n1); test -n "$sln" || { echo "No .sln file found in the repository root"; exit 1; }; msbuild "$sln" /m /p:Configuration=Release /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true'

      - 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: "webdeploy-package"
          path: "**/obj/Release/Package/*.zip"
          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
