name: 🔂 Unit tests

concurrency:
  group: ${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read

jobs:
  generate-jobs-tests:
    name: 💻 Generate test matrix
    runs-on: ubuntu-latest
    outputs:
      sessions: ${{ steps.set-matrix.outputs.sessions }}
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - name: Install uv
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
      - run: uv sync
      - id: set-matrix
        shell: bash
        run: |
          echo sessions=$(
            uv run nox --json -t tests -l |
            jq 'map(
              {
                session,
                name: "\( .name ) on \( .python )\( if .call_spec != {} then " (\(.call_spec | to_entries | map("\(.key)=\(.value)") | join(", ")))" else "" end )"
              }
            )'
          ) | tee --append $GITHUB_OUTPUT

  unit-tests:
    name: 🔬 ${{ matrix.session.name }}
    needs: [generate-jobs-tests]
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        session: ${{ fromJson(needs.generate-jobs-tests.outputs.sessions) }}

    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - name: Install uv
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true

      - run: uv sync
      - run: uv run nox -r -s "${{ matrix.session.session }}"
      - name: Rename coverage data
        if: ${{ always() }}
        run: |
          if [ -f .coverage ]; then
            mv .coverage ".coverage.${{ matrix.session.session }}"
          fi
      - name: Upload coverage data
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: ${{ always() }}
        with:
          name: coverage-data-${{ matrix.session.session }}
          path: ".coverage.${{ matrix.session.session }}"
          include-hidden-files: true
          if-no-files-found: ignore

  coverage:
    name: 📊 Combine & check coverage
    if: always()
    needs: [unit-tests, unit-tests-on-windows]
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
          fetch-depth: 0
      - name: Install uv
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0

      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: coverage-data-*
          merge-multiple: true

      - name: Combine coverage & generate report
        run: |
          uv run --with coverage[toml] --with diff-cover coverage combine
          uv run --with coverage[toml] coverage xml
          uv run --with coverage[toml] coverage html --skip-covered --skip-empty
          uv run --with coverage[toml] coverage report --format=markdown >> $GITHUB_STEP_SUMMARY

      - name: Generate diff coverage report
        if: github.event_name == 'pull_request'
        env:
          BASE_REF: ${{ github.base_ref }}
        run: |
          echo "## Diff Coverage" >> $GITHUB_STEP_SUMMARY
          uv run --with coverage[toml] --with diff-cover diff-cover coverage.xml --compare-branch="origin/$BASE_REF" --markdown-report diff-cover.md --fail-under=0 || true
          cat diff-cover.md >> $GITHUB_STEP_SUMMARY

      - name: Upload HTML report
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: html-report
          path: coverage_html_report

  benchmarks:
    name: 📈 Benchmarks

    # Using this version because CodSpeed doesn't support Ubuntu 24.04 LTS yet
    runs-on: ubuntu-22.04

    permissions:
      contents: read
      id-token: write

    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - name: Install uv
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          python-version: "3.14"

      - run: uv sync

      - name: Run benchmarks
        uses: CodSpeedHQ/action@63f3e98b61959fe67f146a3ff022e4136fe9bb9c # v4.17.6
        with:
          mode: simulation
          run: uv run pytest tests/benchmarks --codspeed -p no:codeflash-benchmark

  lint:
    name: ✨ Lint
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - name: Install uv
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          python-version: "3.12"

      - run: uv sync

      - run: |
          mkdir -p .mypy_cache

          uv run mypy --install-types --non-interactive --cache-dir=.mypy_cache/ --config-file mypy.ini

  unit-tests-on-windows:
    name: 🪟 Tests on Windows
    runs-on: windows-latest

    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - name: Install uv
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          python-version: "3.11"

      - run: uv sync

      # we use uv directly instead of nox since we want to
      # test all integrations at once on windows
      - run: |
          uv run pytest --cov=. --cov-append -n auto --showlocals -vv

      - name: Rename coverage data
        if: ${{ always() }}
        shell: bash
        run: |
          if [ -f .coverage ]; then
            mv .coverage .coverage.windows
          fi

      - name: Upload coverage data
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: ${{ always() }}
        with:
          name: coverage-data-windows
          path: .coverage.windows
          include-hidden-files: true
          if-no-files-found: ignore

  required-ci:
    name: Required CI
    if: ${{ always() }}
    needs:
      - generate-jobs-tests
      - unit-tests
      - unit-tests-on-windows
      - coverage
      - benchmarks
      - lint
    runs-on: ubuntu-latest

    steps:
      - name: Check required jobs
        env:
          NEEDS: ${{ toJson(needs) }}
        run: |
          echo "$NEEDS" | jq -r 'to_entries[] | "\(.key)=\(.value.result)"'
          echo "$NEEDS" | jq -e 'to_entries | all(.value.result == "success")'
