name: CI

on:
  push:
    branches:
      - main
    paths-ignore:
      - '**/*.md'
      - 'docs/**'
      - 'LICENSE'
      - '.cursor/**'
      - '.cursorrules'
      - '.cursorignore'
      - '.devcontainer/**'
      - '.readthedocs.yaml'
      - 'mkdocs.yml'
      - '.github/ISSUE_TEMPLATE/**'
    tags:
      - 'core-v*'
      - 'backend-v*'
      - 'sdk-v*'
      - 'agentensor-v*'
      - 'canvas-v*'
      - 'stack-v*'
  pull_request:
    branches:
      - main
    paths-ignore:
      - '**/*.md'
      - 'docs/**'
      - 'LICENSE'
      - '.cursor/**'
      - '.cursorrules'
      - '.cursorignore'
      - '.devcontainer/**'
      - '.readthedocs.yaml'
      - 'mkdocs.yml'
      - '.github/ISSUE_TEMPLATE/**'
    types:
      - opened
      - synchronize
      - ready_for_review

permissions:
  id-token: write  # Required for PyPI trusted publishing
  contents: read

jobs:
  lint:
    if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false

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

      - name: Setup uv
        uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: Install dependencies
        run: uv sync --all-groups

      - name: Check style against standards
        run: uv run make lint

  coverage:
    if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup uv
        uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: Install dependencies
        run: uv sync --all-groups

      - name: Run unit tests with coverage
        run: |
          uv run coverage run -m pytest
          uv run coverage html --show-contexts --title "Coverage for ${{ github.sha }}"
          uv run coverage xml
          uv run diff-cover coverage.xml --html-report index.html

      - name: Store coverage html
        uses: actions/upload-artifact@v4
        with:
          name: coverage-html
          path: htmlcov
          include-hidden-files: true

      - name: Store diff coverage html
        uses: actions/upload-artifact@v4
        with:
          name: diff-coverage-html
          path: index.html

      - name: Check coverage
        run: |
          uv run coverage report --fail-under 95
          uv run diff-cover coverage.xml --fail-under 100

  postgres-persistence:
    if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd "pg_isready -U postgres"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

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

      - name: Setup uv
        uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: Install dependencies
        run: uv sync --all-groups

      - name: Run Postgres persistence checks
        env:
          ORCHEO_POSTGRES_DSN: postgresql://postgres:postgres@localhost:5432/postgres
          ORCHEO_CHECKPOINT_BACKEND: postgres
        run: |
          uv run pytest tests/integration/test_postgres_persistence.py -q

  build-and-release:
    needs: [lint, coverage, postgres-persistence]
    if: "startsWith(github.ref, 'refs/tags/core-v') || startsWith(github.ref, 'refs/tags/backend-v') || startsWith(github.ref, 'refs/tags/sdk-v') || startsWith(github.ref, 'refs/tags/agentensor-v')"
    runs-on: ubuntu-latest

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

      - name: Setup uv
        uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: Determine package context
        id: package
        run: |
          tag="${GITHUB_REF_NAME}"
          if [[ "$tag" == core-v* ]]; then
            echo "package=orcheo" >> "$GITHUB_OUTPUT"
          elif [[ "$tag" == backend-v* ]]; then
            echo "package=orcheo-backend" >> "$GITHUB_OUTPUT"
          elif [[ "$tag" == sdk-v* ]]; then
            echo "package=orcheo-sdk" >> "$GITHUB_OUTPUT"
          elif [[ "$tag" == agentensor-v* ]]; then
            echo "package=agentensor" >> "$GITHUB_OUTPUT"
          else
            echo "Unsupported tag prefix: $tag" >&2
            exit 1
          fi
          echo "dist_dir=dist/${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"

      - name: Install dependencies
        run: uv sync --all-groups

      - name: Build ${{ steps.package.outputs.package }}
        run: |
          uv build --package "${{ steps.package.outputs.package }}" --out-dir "${{ steps.package.outputs.dist_dir }}"

      - name: Publish ${{ steps.package.outputs.package }} to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: ${{ steps.package.outputs.dist_dir }}

  stack-release:
    needs: [lint, coverage]
    if: "startsWith(github.ref, 'refs/tags/stack-v')"
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Determine stack image metadata
        id: stack
        run: |
          tag="${GITHUB_REF_NAME}"
          version="${tag#stack-v}"
          if [[ "$version" == "$tag" || -z "$version" ]]; then
            echo "Invalid stack tag: $tag" >&2
            exit 1
          fi
          image_repo=$(printf '%s' "ghcr.io/${GITHUB_REPOSITORY_OWNER}/orcheo-stack" | tr '[:upper:]' '[:lower:]')
          echo "version=${version}" >> "$GITHUB_OUTPUT"
          echo "image_repo=${image_repo}" >> "$GITHUB_OUTPUT"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push stack image
        uses: docker/build-push-action@v6
        with:
          context: deploy/stack
          file: deploy/stack/Dockerfile.orcheo
          platforms: linux/amd64,linux/arm64
          push: true
          tags: |
            ${{ steps.stack.outputs.image_repo }}:${{ steps.stack.outputs.version }}
            ${{ steps.stack.outputs.image_repo }}:latest

  canvas:
    if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: apps/canvas
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v6
        with:
          node-version: "20"
          token: ""
      - name: Install dependencies
        run: |
          npm ci
      - name: Lint
        run: npm run lint
      - name: Test
        run: npm run test -- --run

  canvas-build-and-release:
    needs: [canvas]
    if: "startsWith(github.ref, 'refs/tags/canvas-v')"
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: apps/canvas
    permissions:
      contents: read
      id-token: write  # Required for npm trusted publishing and provenance
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v6
        with:
          node-version: "24"
          registry-url: 'https://registry.npmjs.org'
      - name: Upgrade npm
        run: npm install -g npm@^11.5.1
      - name: Install dependencies
        run: |
          npm ci
      - name: Verify version matches tag
        run: |
          tag="${GITHUB_REF_NAME}"
          version="${tag#canvas-v}"
          pkg_version=$(node -p "require('./package.json').version")
          if [[ "$version" != "$pkg_version" ]]; then
            echo "Error: Tag version ($version) does not match package.json version ($pkg_version)" >&2
            exit 1
          fi
          echo "Version check passed: $version"
      - name: Build
        run: npm run build
      - name: Publish to npm
        run: npm publish --provenance --access public
