name: Lint, Test, and Build

on:
  push:
    branches:
      - main
  pull_request:
    types: [opened, synchronize, reopened]

permissions: {}

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  workflow-lint:
    name: Lint GitHub Actions Workflows
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - name: Run zizmor
        uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
      - name: Run actionlint
        uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2

  fix:
    name: Check Ruff Fix
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.11"
      - name: Install uv with caching
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          cache-dependency-glob: |
            **/pyproject.toml
            **/uv.lock
      - name: Create and activate virtual environment
        run: |
          uv venv .venv
          echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
      - name: Install dependencies
        run: uv sync --dev -p .venv --extra dev
      - name: Ruff Check
        run: |
          ruff check --fix-only --diff --exit-non-zero-on-fix || (
            echo ""
            echo "❌ Ruff found issues that can be fixed automatically."
            echo "💡 To fix them locally, run:"
            echo ""
            echo "    pre-commit run --all-files"
            echo ""
            echo "Then commit and push the changes."
            exit 1
          )

  test:
    name: Run Tests
    runs-on: ubuntu-latest
    permissions:
      contents: read
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install Deno
        uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
        with:
          deno-version: v2.7.7
      - name: Verify Deno installation
        run: deno --version
      - name: Install uv with caching
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          cache-dependency-glob: |
            **/pyproject.toml
            **/uv.lock
      - name: Create and activate virtual environment
        run: |
          uv venv .venv
          echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
      - name: Install dependencies
        run: |
          uv sync --dev -p .venv --extra dev
          uv pip list
      - name: Run tests with pytest
        run: uv run -p .venv pytest -vv -m 'not extra and not deno' -n auto --dist worksteal tests/

  extra_test:
    name: Run Extra and Deno Tests
    runs-on: ubuntu-latest
    permissions:
      contents: read
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install Deno
        uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
        with:
          deno-version: v2.7.7
      - name: Verify Deno installation
        run: deno --version
      - name: Install uv with caching
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          cache-dependency-glob: |
            **/pyproject.toml
            **/uv.lock
      - name: Create and activate virtual environment
        run: |
          uv venv .venv
          echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
      - name: Install dependencies
        run: uv sync -p .venv --extra dev --extra test_extras
      - name: Run extra and deno tests
        run: uv run -p .venv pytest tests/ -m 'extra or deno' --extra --deno -n auto --dist worksteal

  llm_call_test:
    name: Run Tests with Real LM
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: 3.11
      - name: Prefetch Ollama image
        run: |
          (
            if docker pull ollama/ollama:0.31.2 > "$RUNNER_TEMP/ollama-pull.log" 2>&1; then
              echo 0 > "$RUNNER_TEMP/ollama-pull.status"
            else
              echo "$?" > "$RUNNER_TEMP/ollama-pull.status"
            fi
          ) &
      - name: Install uv with caching
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          cache-dependency-glob: |
            **/pyproject.toml
            **/uv.lock
      - name: Create and activate virtual environment
        run: |
          uv venv .venv
          echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
      - name: Install dependencies
        run: |
          uv sync --dev -p .venv --extra dev
          uv pip list
      - name: Cache Ollama models
        id: cache-ollama
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: ollama-data
          key: ollama-llama3.2-3b-${{ runner.os }}-ollama0.31.2-v2
      - name: Start Ollama service
        run: |
          for _ in {1..120}; do
            [[ -f "$RUNNER_TEMP/ollama-pull.status" ]] && break
            sleep 1
          done
          if [[ ! -f "$RUNNER_TEMP/ollama-pull.status" ]] || [[ "$(cat "$RUNNER_TEMP/ollama-pull.status")" != 0 ]]; then
            cat "$RUNNER_TEMP/ollama-pull.log"
            exit 1
          fi
          mkdir -p ollama-data
          docker run -d --name ollama \
            -p 11434:11434 \
            -v "$GITHUB_WORKSPACE/ollama-data:/root/.ollama" \
            ollama/ollama:0.31.2
          # Some GitHub runners are Sapphire-Rapids-class machines with AMX. llama.cpp's
          # AMX-repacked weight path segfaults there during model load (llama-server dies
          # with SIGSEGV in warm-up), while all non-AMX runners pass. Removing the AMX
          # backend variant makes ggml's autodetection fall back to the icelake kernels
          # that every other runner already uses.
          docker exec ollama rm /usr/lib/ollama/libggml-cpu-sapphirerapids.so
          timeout 60 bash -c 'until curl -f http://localhost:11434/api/version; do sleep 2; done'
      - name: Pull LLM
        if: steps.cache-ollama.outputs.cache-hit != 'true'
        run: docker exec ollama ollama pull llama3.2:3b
      - name: Warm up LLM
        run: |
          for i in 1 2 3; do
            if curl -fsS http://localhost:11434/api/generate \
              -d '{"model":"llama3.2:3b","prompt":"ping","stream":false}'; then
              echo "Ollama warm-up succeeded"
              exit 0
            fi
            echo "Warm-up attempt $i failed; retrying..."
            sleep 5
          done
          echo "Ollama failed to serve llama3.2:3b after 3 attempts"
          docker logs ollama || true
          exit 1
      - name: Set LM environment variable
        run: echo "LM_FOR_TEST=ollama/llama3.2:3b" >> "$GITHUB_ENV"
      - name: Run tests
        run: uv run -p .venv pytest -m llm_call --llm_call -vv --durations=5 tests/
      - name: Fix permissions for cache
        if: always()
        run: sudo chown -R "$USER":"$USER" ollama-data || true
      - name: Stop Ollama service
        if: always()
        run: docker stop ollama && docker rm ollama

  build_package:
    name: Build Package
    runs-on: ubuntu-latest
    permissions:
      contents: read
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install uv with caching
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          cache-dependency-glob: |
            **/pyproject.toml
            **/uv.lock
      - name: Build
        run: uv build
      - name: Install built package
        run: |
          uv venv .venv
          uv pip install dist/*.whl -p .venv
      - name: Test import dspy
        run: .venv/bin/python -c "import dspy"
