name: CI Pipeline

on:
  workflow_dispatch:
  push:
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.forgejo/ISSUE_TEMPLATE/**'
      - '**/*.md'
  pull_request:
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.forgejo/ISSUE_TEMPLATE/**'
      - '**/*.md'

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

permissions:
  contents: read
  security-events: write
  actions: read
  packages: write

env:
  GO_VERSION: '1.25.0'
  ZIG_VERSION: '0.15.2'
  RSYNC_VERSION: '3.3.0'

jobs:
  test:
    name: Test (ubuntu-latest on self-hosted)
    runs-on: self-hosted
    timeout-minutes: 30

    services:
      redis:
        image: redis:7
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Go
      run: |
        REQUIRED_GO="1.25.0"
        if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then
          echo "Go ${REQUIRED_GO} already installed - skipping download"
        else
          echo "Installing Go ${REQUIRED_GO}..."
          curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
          export PATH="/usr/local/go/bin:$PATH"
          echo "/usr/local/go/bin" >> $GITHUB_PATH
          echo "Go ${REQUIRED_GO} installed"
        fi
        go version

    - name: Set up Zig
      run: |
        ZIG_VERSION="${{ env.ZIG_VERSION }}"
        if command -v zig &> /dev/null && zig version | grep -q "${ZIG_VERSION}"; then
          echo "Zig ${ZIG_VERSION} already installed - skipping download"
        else
          echo "Installing Zig ${ZIG_VERSION}..."
          ZIG_DIR="/usr/local/zig-${ZIG_VERSION}"
          if [[ "$OSTYPE" == "linux-gnu"* ]]; then
            curl -fsSL --retry 3 "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz
            sudo mkdir -p "${ZIG_DIR}"
            sudo tar -C "${ZIG_DIR}" --strip-components=1 -xJf /tmp/zig.tar.xz
            sudo ln -sf "${ZIG_DIR}/zig" /usr/local/bin/zig
          elif [[ "$OSTYPE" == "darwin"* ]]; then
            curl -fsSL --retry 3 "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-macos-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz
            sudo mkdir -p "${ZIG_DIR}"
            sudo tar -C "${ZIG_DIR}" --strip-components=1 -xJf /tmp/zig.tar.xz
            sudo ln -sf "${ZIG_DIR}/zig" /usr/local/bin/zig
          fi
          rm -f /tmp/zig.tar.xz
          echo "Zig ${ZIG_VERSION} installed"
        fi
        zig version

    - name: Install dependencies
      run: |
        go mod download
        sudo apt-get update
        sudo apt-get install -y podman redis-tools build-essential autoconf automake libtool pkg-config musl-tools

    - name: Build pinned rsync from official source (for CLI tests)
      run: |
        make -C cli build-rsync RSYNC_VERSION=${{ env.RSYNC_VERSION }}

    - name: Verify dependencies
      run: go mod verify

    - name: Run tests
      run: make test

    - name: Test internal/queue package
      run: go test -v -race -coverprofile=queue-coverage.out ./internal/queue/...

    - name: Run comprehensive tests
      run: make test

    - name: Run linters
      run: make lint

    - name: Security lint checks
      run: |
        echo "=== Security Lint Checks ==="
        echo "Checking for unsafe os.WriteFile usage..."
        if grep -rn "os\.WriteFile" internal/ --include="*.go" | grep -v "_test.go" | grep -v "// fsync-exempt"; then
          echo "ERROR: Found os.WriteFile calls. Use fileutil.WriteFileSafe() instead."
          echo "Mark exemptions with '// fsync-exempt' comment"
          exit 1
        fi
        echo "✓ No unsafe os.WriteFile calls found"
        
        echo "Checking for O_NOFOLLOW in sensitive paths..."
        if grep -rn "os\.OpenFile.*O_CREATE" internal/queue/ internal/crypto/ internal/experiment/ --include="*.go" | grep -v "OpenFileNoFollow" | grep -v "_test.go"; then
          echo "WARNING: File open in sensitive dir may need O_NOFOLLOW"
        fi
        echo "✓ O_NOFOLLOW check complete"

    - name: Generate coverage report
      run: make test-coverage

  dev-smoke:
    name: Dev Compose Smoke Test
    runs-on: self-hosted
    needs: test
    timeout-minutes: 20

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Run dev smoke test
      run: make dev-smoke

  security-scan:
    name: Security Scan
    runs-on: self-hosted
    needs: test
    timeout-minutes: 20

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Go
      run: |
        REQUIRED_GO="1.25.0"
        if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then
          echo "Go ${REQUIRED_GO} already installed"
        else
          echo "Installing Go ${REQUIRED_GO}..."
          curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
          export PATH="/usr/local/go/bin:$PATH"
          echo "/usr/local/go/bin" >> $GITHUB_PATH
          echo "Go ${REQUIRED_GO} installed"
        fi
        go version

    - name: Install security scanners
      run: |
        # Install gosec
        curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sudo sh -s -- -b /usr/local/bin latest
        # Install nancy
        curl -sfL https://raw.githubusercontent.com/sonatype-nexus-community/nancy/master/install.sh | sudo sh -s -- -b /usr/local/bin latest
        # Install trivy
        curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin latest

    - name: Go source security scan (gosec)
      run: |
        echo "Running gosec security scanner..."
        gosec -fmt sarif -out gosec-results.sarif ./... || {
          echo "gosec found issues - check gosec-results.sarif"
          exit 1
        }
      continue-on-error: false

    - name: Dependency audit (nancy)
      run: |
        echo "Running nancy dependency audit..."
        go list -json -deps ./... | nancy sleuth --output sarif > nancy-results.sarif || {
          echo "nancy found vulnerable dependencies"
          cat nancy-results.sarif
          exit 1
        }
      continue-on-error: false

    - name: Upload security scan results
      uses: actions/upload-artifact@v4
      if: always()
      with:
        name: security-scan-results
        path: |
          gosec-results.sarif
          nancy-results.sarif
        retention-days: 30

  test-scripts:
    name: Test Scripts
    runs-on: self-hosted
    needs: test
    timeout-minutes: 15

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Install dependencies
      run: |
        sudo apt-get update
        sudo apt-get install -y podman redis-tools bats

    - name: Test scripts
      run: |
        chmod +x scripts/*.sh || true
        chmod +x scripts/maintenance/*.sh || true

        ./scripts/verify_release.sh --help
        ./scripts/manage-artifacts.sh help
        ./scripts/track_performance.sh --help
        ./scripts/smoke-test.sh --help

  test-native:
    name: Test Native Libraries
    runs-on: self-hosted
    needs: native-build-matrix
    timeout-minutes: 30

    services:
      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 5s
          --health-timeout 3s
          --health-retries 3

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Install cmake and build tools
        run: |
          echo "Installing cmake and build dependencies..."
          if [[ "$OSTYPE" == "linux-gnu"* ]]; then
            if command -v apt-get &> /dev/null; then
              sudo apt-get update
              sudo apt-get install -y cmake zlib1g-dev build-essential
            elif command -v yum &> /dev/null; then
              sudo yum install -y cmake zlib-devel gcc-c++
            fi
          elif [[ "$OSTYPE" == "darwin"* ]]; then
            brew install cmake zlib
          fi
          which cmake

      - name: Setup Go
        run: |
          REQUIRED_GO="1.25.0"
          if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then
            echo "Go ${REQUIRED_GO} already installed - skipping download"
          else
            echo "Installing Go ${REQUIRED_GO}..."
            if [[ "$OSTYPE" == "linux-gnu"* ]]; then
              curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
            elif [[ "$OSTYPE" == "darwin"* ]]; then
              curl -sL "https://go.dev/dl/go${REQUIRED_GO}.darwin-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
            fi
            export PATH="/usr/local/go/bin:$PATH"
            echo "/usr/local/go/bin" >> $GITHUB_PATH
            echo "Go ${REQUIRED_GO} installed"
          fi
          go version

      - name: Build Native Libraries
        run: |
          echo "Building native C++ libraries..."
          make native-build 2>&1 || {
            echo ""
            echo "Native build failed!"
            echo ""
            echo "Common causes:"
            echo "  1. Missing cmake: Install with 'apt-get install cmake'"
            echo "  2. Missing C++ compiler: Install with 'apt-get install build-essential'"
            echo "  3. Missing zlib: Install with 'apt-get install zlib1g-dev'"
            echo "  4. CMakeLists.txt not found: Ensure native/CMakeLists.txt exists"
            echo ""
            exit 1
          }
          echo "Native libraries built successfully"

      - name: Test with Native Libraries
        run: |
          echo "Running tests WITH native libraries enabled..."
          CGO_ENABLED=1 go test -tags native_libs -v ./tests/... || true

      - name: Native Smoke Test
        run: |
          echo "Running native libraries smoke test..."
          CGO_ENABLED=1 go test -tags native_libs ./tests/benchmarks/... -run TestNative || true

      - name: Test Fallback (Go only)
        run: |
          echo "Running tests WITHOUT native libraries (Go fallback)..."
          go test -v ./tests/... || true

      - name: Run Benchmarks
        run: |
          echo "Running performance benchmarks..."
          echo "=== Go Implementation ==="
          go test -bench=. ./tests/benchmarks/ -benchmem || true
          echo ""
          echo "=== Native Implementation ==="
          CGO_ENABLED=1 go test -tags native_libs -bench=. ./tests/benchmarks/ -benchmem || true

  native-build-matrix:
    name: Native Library Build Matrix
    runs-on: self-hosted
    needs: test
    timeout-minutes: 30
    strategy:
      matrix:
        build_config:
          - name: "native"
            tags: "native_libs"
            cgo_enabled: "1"
            build_native: "true"
          - name: "cgo-only"
            tags: ""
            cgo_enabled: "1"
            build_native: "false"
          - name: "no-cgo"
            tags: ""
            cgo_enabled: "0"
            build_native: "false"
      fail-fast: false

    services:
      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 5s
          --health-timeout 3s
          --health-retries 3

    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      with:
        fetch-depth: 1

    - name: Install cmake and build tools
      if: matrix.build_config.build_native == 'true'
      run: |
        echo "Installing cmake and build dependencies..."
        if [[ "$OSTYPE" == "linux-gnu"* ]]; then
          if command -v apt-get &> /dev/null; then
            sudo apt-get update
            sudo apt-get install -y cmake zlib1g-dev build-essential
          elif command -v yum &> /dev/null; then
            sudo yum install -y cmake zlib-devel gcc-c++
          fi
        elif [[ "$OSTYPE" == "darwin"* ]]; then
          brew install cmake zlib
        fi
        which cmake

    - name: Setup Go
      run: |
        REQUIRED_GO="1.25.0"
        if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then
          echo "Go ${REQUIRED_GO} already installed"
        else
          echo "Installing Go ${REQUIRED_GO}..."
          curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
          export PATH="/usr/local/go/bin:$PATH"
          echo "/usr/local/go/bin" >> $GITHUB_PATH
          echo "Go ${REQUIRED_GO} installed"
        fi
        go version

    - name: Build Native Libraries
      if: matrix.build_config.build_native == 'true'
      run: |
        echo "Building native C++ libraries..."
        make native-build 2>&1 || {
          echo ""
          echo "Native build failed!"
          echo ""
          echo "Common causes:"
          echo "  1. Missing cmake: Install with 'apt-get install cmake'"
          echo "  2. Missing C++ compiler: Install with 'apt-get install build-essential'"
          echo "  3. Missing zlib: Install with 'apt-get install zlib1g-dev'"
          echo "  4. CMakeLists.txt not found: Ensure native/CMakeLists.txt exists"
          echo ""
          exit 1
        }
        echo "Native libraries built successfully"

    - name: Run tests - ${{ matrix.build_config.name }}
      run: |
        echo "=== Testing ${{ matrix.build_config.name }} build (CGO_ENABLED=${{ matrix.build_config.cgo_enabled }}, tags=${{ matrix.build_config.tags }}) ==="
        CGO_ENABLED=${{ matrix.build_config.cgo_enabled }} go test -tags "${{ matrix.build_config.tags }}" -v ./tests/unit/... || true

    - name: Run plugin quota tests
      run: |
        echo "=== Running Plugin GPU Quota tests ==="
        go test -v ./tests/unit/scheduler/... -run TestPluginQuota

    - name: Run service templates tests
      run: |
        echo "=== Running Service Templates tests ==="
        go test -v ./tests/unit/scheduler/... -run TestServiceTemplate

    - name: Run scheduler tests
      run: |
        echo "=== Running Scheduler tests ==="
        go test -v ./tests/unit/scheduler/... -run TestScheduler

    - name: Run vLLM plugin tests
      run: |
        echo "=== Running vLLM Plugin tests ==="
        go test -v ./tests/unit/worker/plugins/... -run TestVLLM

    - name: Run audit tests
      run: |
        echo "=== Running Audit Logging tests ==="
        go test -v ./tests/unit/security/... -run TestAudit
        go test -v ./tests/integration/audit/...

  build-trigger:
    name: Trigger Build Workflow
    runs-on: self-hosted
    needs: [test, security-scan, native-build-matrix, dev-smoke, test-scripts]
    if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'
    timeout-minutes: 5

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Trigger build workflow
      run: |
        echo "All CI checks passed. Build workflow will be triggered."
        echo "SHA: ${{ gitea.sha }}"
        echo "Ref: ${{ gitea.ref }}"
        echo "Repository: ${{ gitea.repository }}"
