name: CI

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

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.12"

      - name: Install uv
        uses: astral-sh/setup-uv@v8.0.0

      - name: Install dependencies
        run: uv sync --extra dev --extra framework-comparison --extra server

      - name: Ruff check
        run: uv run ruff check src/ tests/

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.12"

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cargo cache
        uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            rust/target
          key: rust-${{ runner.os }}-${{ hashFiles('rust/Cargo.lock') }}
          restore-keys: rust-${{ runner.os }}-

      - name: Install uv
        uses: astral-sh/setup-uv@v8.0.0

      - name: Install dependencies
        run: uv sync --extra dev --extra framework-comparison --extra server

      - name: Build Rust extension
        run: uv run maturin develop --manifest-path rust/crates/openjarvis-python/Cargo.toml

      - name: Run tests
        run: |
          uv run pytest tests/ -v --tb=short -m "not live and not cloud and not hub" \
            --cov=openjarvis \
            --cov-report=term-missing \
            --cov-report=xml \
            --cov-fail-under=60

      - name: Upload coverage report
        if: always()
        continue-on-error: true
        timeout-minutes: 5
        uses: actions/upload-artifact@v4
        with:
          name: coverage-xml
          path: coverage.xml
          if-no-files-found: warn

  # Windows job — empirically exercises the platform-specific code paths that
  # the Ubuntu `test` job can never reach: GlobalMemoryStatusEx RAM detection
  # (#373) and the cp9xx -> UTF-8 stdout reconfigure (#293). Also the only CI
  # job that builds + imports the mandatory `openjarvis_rust` PyO3 extension
  # on Windows. Public repo -> Windows runner minutes are free.
  #
  # All `run:` steps use static commands only (no `github.event.*`
  # interpolation), so there is no workflow-injection surface here.
  test-windows:
    runs-on: windows-latest
    strategy:
      fail-fast: false
      matrix:
        # 3.12 = common; 3.13 = the supported ceiling — installing there guards
        # against a numpy/native wheel gap at the top of the range (#350), which
        # is exactly how the source-build failure slips in on Windows.
        python-version: ["3.12", "3.13"]
    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install uv
        uses: astral-sh/setup-uv@v8.0.0

      - name: Install dependencies
        run: uv sync --extra dev --extra server

      # Pure-Python check — runs before the Rust build so a flaky toolchain
      # install can never mask the actual RAM-detection verification.
      - name: Verify Windows RAM detection (#373)
        shell: bash
        run: |
          uv run python -c "from openjarvis.core.config import _total_ram_gb; ram = _total_ram_gb(); print(f'GlobalMemoryStatusEx RAM = {ram} GB'); assert ram > 0, f'Windows RAM detection returned {ram}, expected > 0'"

      - name: Run Windows-specific tests (hardware + CLI)
        shell: bash
        run: |
          uv run pytest tests/hardware/test_hardware_profiles.py tests/cli/test_cli.py -v -m "not live and not cloud"

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Build + import the PyO3 extension on Windows
        shell: bash
        run: |
          uv run maturin develop --manifest-path rust/crates/openjarvis-python/Cargo.toml
          uv run python -c "import openjarvis_rust; print('openjarvis_rust imports on Windows OK')"

      - name: Smoke-test CLI
        shell: bash
        run: |
          uv run jarvis --version

  rust:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: rust
    steps:
      - uses: actions/checkout@v6

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy

      - name: Cargo cache
        uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            rust/target
          key: rust-${{ runner.os }}-${{ hashFiles('rust/Cargo.lock') }}
          restore-keys: rust-${{ runner.os }}-

      - name: Clippy
        run: cargo clippy --workspace --all-targets -- -D warnings

      - name: Test
        run: cargo test --workspace
