pipeline {
  agent none
  options { ansiColor('xterm'); timestamps() }

  environment {
    DOTNET_CLI_TELEMETRY_OPTOUT = '1'
    DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1'
    SONAR_PROJECT_KEY  = 'as400api-dotnet'
    SONAR_PROJECT_NAME = 'AS400_API_DOTNET (.NET 9)'
  }

  stages {
    stage('Checkout') {
      agent any
      steps { checkout scm }
    }

    stage('Install prerequisites') {
      agent any
      steps {
        sh '''
          set -euo pipefail

          install_deps() {
            if command -v apt-get >/dev/null 2>&1; then
              export DEBIAN_FRONTEND=noninteractive
              apt-get update
              # Common native deps for .NET + tools
              apt-get install -y --no-install-recommends \
                libkrb5-3 zlib1g libstdc++6 ca-certificates curl unzip jq

              # Java (for OWASP Dependency-Check)
              # prefer 17 headless; fall back to default-jre-headless
              (apt-get install -y --no-install-recommends openjdk-17-jre-headless) || \
              (apt-get install -y --no-install-recommends default-jre-headless) || true

              # ---- ICU on Debian/Ubuntu ----
              # 1) try meta/dev names (older releases)
              apt-get install -y --no-install-recommends libicu || true
              apt-get install -y --no-install-recommends libicu-dev || true

              # 2) if still missing, detect versioned package (e.g., libicu74 on trixie)
              if ! ldconfig -p 2>/dev/null | grep -qi 'libicu'; then
                PKG="$(apt-cache search -n '^libicu[0-9]+$' | awk '{print $1}' | sort -V | tail -n1 || true)"
                if [ -n "${PKG:-}" ]; then
                  echo "Installing detected ICU package: $PKG"
                  apt-get install -y --no-install-recommends "$PKG"
                fi
              fi

              # 3) final sanity
              if ! ldconfig -p 2>/dev/null | grep -qi 'libicu'; then
                echo "WARN: ICU not found after install attempts (will rely on invariant mode if needed)"
              fi

            elif command -v dnf >/dev/null 2>&1; then
              dnf install -y libicu krb5-libs zlib libstdc++ ca-certificates curl unzip java-17-openjdk-headless jq || true
            elif command -v yum >/dev/null 2>&1; then
              yum install -y libicu krb5-libs zlib libstdc++ ca-certificates curl unzip java-17-openjdk-headless jq || true
            elif command -v apk >/dev/null 2>&1; then
              apk add --no-cache icu-libs krb5-libs zlib libstdc++ ca-certificates curl unzip openjdk17-jre-headless jq || true
            else
              echo "Unsupported package manager. Please install ICU + Java manually."
              exit 1
            fi
          }

          install_deps
        '''
      }
    }

    

    // Bootstrap .NET SDK if dotnet is missing
    stage('Bootstrap .NET SDK') {
      agent any
      steps {
        sh '''
          set -e
          if ! command -v dotnet >/dev/null 2>&1; then
            echo "Installing .NET SDK locally for this build..."
            curl -fsSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh
            bash dotnet-install.sh --channel 9.0 --install-dir "$HOME/.dotnet"
          fi
          export PATH="$HOME/.dotnet:$PATH"

          # If ICU still missing (e.g., no libicu*.so found), enable invariant mode as a fallback
          if ! ldconfig -p 2>/dev/null | grep -qi 'libicu'; then
            export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
            echo "NOTE: Running .NET in globalization invariant mode (ICU not found)"
          fi

          dotnet --info
        '''
      }
    }

    stage('SCA (NuGet vulnerabilities + OWASP)') {
      agent any
      steps {
        sh '''
          set -euo pipefail
          export PATH="$HOME/.dotnet:$PATH"

          echo "=== NuGet vulnerability audit ==="
          dotnet restore
          dotnet list package --vulnerable || true

          echo "=== OWASP Dependency-Check (no Docker) ==="
          rm -f depcheck.zip || true
          rm -rf dependency-check || true
          mkdir -p depcheck

          API="https://api.github.com/repos/jeremylong/DependencyCheck/releases/latest"

          # Resolve the correct asset URL (ends with -release.zip)
          echo "Resolving Dependency-Check latest asset URL from GitHub API..."
          ASSET_URL="$(curl -fsSL "$API" \
            | jq -r '.assets[]?.browser_download_url | select(test("release\\\\.zip$"))' \
            | head -n1 || true)"

          # Fallback from tag_name if assets listing is throttled
          if [ -z "${ASSET_URL:-}" ]; then
            TAG="$(curl -fsSL "$API" | jq -r '.tag_name' || true)"
            if [ -n "${TAG:-}" ]; then
              VER="${TAG#v}"
              ASSET_URL="https://github.com/jeremylong/DependencyCheck/releases/download/${TAG}/dependency-check-${VER}-release.zip"
            fi
          fi

          if [ -z "${ASSET_URL:-}" ]; then
            echo "ERROR: Could not resolve Dependency-Check release asset URL."
            exit 9
          fi

          echo "Downloading: $ASSET_URL"
          curl -fL --retry 3 --retry-all-errors -o depcheck.zip "$ASSET_URL"

          # Validate and extract
          unzip -tq depcheck.zip || { echo "Downloaded file is not a valid ZIP"; exit 9; }
          mkdir -p dependency-check
          unzip -q depcheck.zip -d dependency-check

          DC_BIN="$(echo dependency-check/dependency-check*/bin/dependency-check.sh)"
          if [ ! -x "$DC_BIN" ]; then
            echo "ERROR: dependency-check.sh not found under extracted folder"
            ls -la dependency-check || true
            exit 9
          fi

          # Generate HTML and XML reports (note: use multiple -f flags)
          bash "$DC_BIN" \
            -f HTML -f XML \
            --project "AS400_API_DOTNET" \
            --scan "." \
            --out "depcheck" \
            --noupdate || true

          echo "SCA reports generated in depcheck/"
        '''
      }
      post {
        always {
          archiveArtifacts artifacts: 'depcheck/**', allowEmptyArchive: true
        }
      }
    }

    stage('SAST + Coverage (SonarQube + Tests)') {
      agent any
      steps {
        sh '''
          set -e
          export PATH="$HOME/.dotnet:$PATH"

          # run tests with coverage (cobertura) + produce TRX results for JUnit
          dotnet test \
            --logger "trx;LogFileName=test_results.trx" \
            /p:CollectCoverage=true \
            /p:CoverletOutput=coverage/ \
            /p:CoverletOutputFormat=cobertura

          mkdir -p coverage-report
          # copy the cobertura file (adjust path if your solution layout differs)
          COBERTURA_FILE=$(find . -type f -name "coverage.cobertura.xml" | head -n1 || true)
          [ -n "$COBERTURA_FILE" ] && cp "$COBERTURA_FILE" coverage-report/Cobertura.xml || true

          # If SonarQube is configured, run scanner; otherwise skip gracefully.
          if [ -n "${SONARQUBE_ENV_NAME:-}" ]; then
            echo "SonarQube env variable detected: $SONARQUBE_ENV_NAME"
          else
            echo "SonarQube not configured; skipping Sonar scan."
            exit 0
          fi
        '''
      }
      post {
        always {
          // Publish TRX results (built-in)
          junit '**/TestResults/**/*.trx'
          // Archive coverage XML so you can inspect it
          archiveArtifacts artifacts: 'coverage-report/**', allowEmptyArchive: true
        }
      }
    }

    stage('Build Artifact') {
      agent any
      steps {
        sh '''
          set -e
          export PATH="$HOME/.dotnet:$PATH"
          dotnet publish -c Release -o out
          ls -la out
        '''
        archiveArtifacts artifacts: 'out/**', fingerprint: true
      }
    }
  }
}