name: Tests

on:
  pull_request:
    branches:
      - master

jobs:
  linting:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Install the latest version of uv
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
      - name: Check format
        run: uv run --frozen ruff format --diff
      - name: Linting
        run: uv run --frozen ruff check
      - name: Type check
        run: uv run --frozen ty check -- nextcloud_mcp_server

  unit-test:
    runs-on: ubuntu-latest
    needs: [linting]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Install the latest version of uv
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
      - name: Run unit tests
        run: uv run pytest -v -m unit -o "addopts=-p no:asyncio"

  integration-test:
    runs-on: ubuntu-latest
    needs: [linting]
    strategy:
      fail-fast: false
      matrix:
        nextcloud_version:
          - "31"
          - "32"
          # - "33"  # Disabled until all upstream apps support NC 33
        mode:
          - "single-user"
          - "multi-user-basic"
          - "login-flow"
          - "keycloak"
        include:
          # Version-specific image pins — Renovate updates these via customManagers in renovate.json
          # Each entry is pinned to its major version (e.g., NC 31 only gets 31.x updates)
          - nextcloud_version: "31"
            nextcloud_image: "docker.io/library/nextcloud:31.0.14@sha256:07ec73cc816e58d6f45a162cd53ef886462c29271a23fc68d0124cec276e3767"
          - nextcloud_version: "32"
            nextcloud_image: "docker.io/library/nextcloud:32.0.9@sha256:fdca48a6025e63d7b52913cad8ddf341c37b9dd8d03c4962ea0a1ec0c42c7442"
          # Disabled until all upstream apps support NC 33
          # - nextcloud_version: "33"
          #   nextcloud_image: "docker.io/library/nextcloud:33.0.3@sha256:76f04e434a82572dbfee7ad6dca313229eade89ee538fccd2bf6396f4440d48b"

          # Mode-specific properties
          - mode: single-user
            profile: single-user
            markers: "(smoke and not keycloak and not login_flow and not multi_user_basic) or (integration and not keycloak and not login_flow and not multi_user_basic)"
            wait-port: 8000
            mcp-internal-url: "http://mcp:8000"
            needs-playwright: false
            extra-args: >-
              --ignore=tests/integration/test_qdrant_collection_creation.py
              --ignore=tests/rag_evaluation/

          - mode: multi-user-basic
            profile: multi-user-basic
            markers: "multi_user_basic"
            wait-port: 8003
            mcp-internal-url: "http://mcp-multi-user-basic:8000"
            needs-playwright: true
            extra-args: ""

          - mode: login-flow
            profile: login-flow
            markers: "login_flow"
            wait-port: 8004
            mcp-internal-url: "http://mcp-login-flow:8004"
            needs-playwright: true
            extra-args: ""

          - mode: keycloak
            profile: keycloak
            markers: "keycloak"
            wait-port: 8002
            mcp-internal-url: "http://mcp-keycloak:8002"
            needs-playwright: true
            extra-args: ""

    name: integration (${{ matrix.mode }} / nc${{ matrix.nextcloud_version }})

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: 'true'

      - name: Set up PHP 8.4
        if: matrix.mode != 'single-user'
        uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
        with:
          php-version: 8.4
          coverage: none

      # OIDC app installed from app store (dev mount removed from docker-compose.yml)

      - name: Set up Node.js
        if: matrix.mode != 'single-user'
        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
        with:
          node-version: 24

      - name: Build Astrolabe app
        if: matrix.mode != 'single-user'
        run: |
          cd third_party/astrolabe
          composer install --no-dev --optimize-autoloader
          npm ci
          npm run build

      # OIDC app is now installed from the Nextcloud app store via app-hook
      # (third_party/oidc fork no longer mounted — upstream v1.16.3 fixes PR #631)
      # - name: Build OIDC app
      #   run: |
      #     cd third_party/oidc
      #     composer install --no-dev --optimize-autoloader
      #     npm ci
      #     npm run build

      # Start services with the appropriate profile
      - name: Run docker compose
        uses: hoverkraft-tech/compose-action@4894d2492015c1774ee5a13a95b1072093087ec3 # v2.5.0
        with:
          compose-file: "./docker-compose.yml"
          compose-flags: "--profile ${{ matrix.profile }}"
          up-flags: "--build"
        env:
          MCP_SERVER_URL: ${{ matrix.mcp-internal-url }}
          NEXTCLOUD_IMAGE: ${{ matrix.nextcloud_image }}

      - name: Install the latest version of uv
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0

      - name: Install Playwright
        if: matrix.needs-playwright
        run: uv run playwright install chromium --with-deps

      # Wait for Nextcloud to be healthy
      - name: Wait for Nextcloud
        run: |
          echo "Waiting for Nextcloud at http://localhost:8080..."
          max_attempts=60
          attempt=0
          until curl -sSf http://localhost:8080/status.php 2>/dev/null | grep -q '"installed":true'; do
            attempt=$((attempt + 1))
            if [ $attempt -ge $max_attempts ]; then
              echo "Nextcloud did not become ready in time."
              docker compose logs app
              exit 1
            fi
            echo "Attempt $attempt/$max_attempts: Not ready, sleeping 5s..."
            sleep 5
          done
          echo "Nextcloud is ready."

      # Wait for the MCP service to be healthy
      - name: Wait for MCP service (${{ matrix.mode }})
        run: |
          echo "Waiting for MCP service on port ${{ matrix.wait-port }}..."
          max_attempts=30
          attempt=0
          until curl -o /dev/null -s -w "%{http_code}\n" http://localhost:${{ matrix.wait-port }}/health 2>/dev/null | grep -qE "200|404|405"; do
            attempt=$((attempt + 1))
            if [ $attempt -ge $max_attempts ]; then
              echo "MCP service did not become ready in time."
              docker compose --profile ${{ matrix.profile }} logs
              exit 1
            fi
            echo "Attempt $attempt/$max_attempts: Not ready, sleeping 5s..."
            sleep 5
          done
          echo "MCP service is ready on port ${{ matrix.wait-port }}."

      - name: Wait for Keycloak
        if: matrix.mode == 'keycloak'
        run: |
          echo "Waiting for Keycloak realm at http://localhost:8888..."
          max_attempts=30
          attempt=0
          until curl -sf http://localhost:8888/realms/nextcloud-mcp > /dev/null 2>&1; do
            attempt=$((attempt + 1))
            if [ $attempt -ge $max_attempts ]; then
              echo "Keycloak did not become ready in time."
              docker compose --profile keycloak logs keycloak
              exit 1
            fi
            echo "Attempt $attempt/$max_attempts: Not ready, sleeping 5s..."
            sleep 5
          done
          echo "Keycloak is ready."

      - name: Verify OIDC configuration
        if: matrix.mode == 'login-flow' || matrix.mode == 'keycloak'
        run: |
          echo "=== OIDC Discovery ==="
          curl -s http://localhost:8080/.well-known/openid-configuration | jq .
          echo "=== OIDC App Status ==="
          docker compose exec -T app php occ app:list --output=json 2>/dev/null | jq '.enabled.oidc // "NOT INSTALLED"'

      - name: Verify Keycloak realm
        if: matrix.mode == 'keycloak'
        run: |
          echo "=== Keycloak Realm Discovery ==="
          curl -s http://localhost:8888/realms/nextcloud-mcp/.well-known/openid-configuration | jq .
          echo "=== Keycloak Provider in Nextcloud ==="
          docker compose exec -T app php occ user_oidc:provider keycloak 2>/dev/null || echo "Provider not yet configured"

      - name: Run tests (${{ matrix.mode }})
        env:
          NEXTCLOUD_HOST: "http://localhost:8080"
          NEXTCLOUD_USERNAME: "admin"
          NEXTCLOUD_PASSWORD: "admin"
        run: |
          uv run pytest -v \
            --log-cli-level=WARN \
            -m '${{ matrix.markers }}' \
            -o "addopts=-p no:asyncio" \
            --timeout=300 \
            ${{ matrix.extra-args }}

      - name: Collect service logs on failure
        if: failure()
        run: |
          docker compose --profile ${{ matrix.profile }} logs --tail=500 > /tmp/docker-compose-logs.txt 2>&1
          docker compose exec -T app cat /var/www/html/data/nextcloud.log 2>/dev/null | tail -100 > /tmp/nextcloud-app.log 2>&1 || true

      - name: Upload debug artifacts
        if: failure()
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        with:
          name: debug-${{ matrix.mode }}-nc${{ matrix.nextcloud_version }}
          path: |
            /tmp/*.png
            /tmp/docker-compose-logs.txt
            /tmp/nextcloud-app.log
          retention-days: 7
          if-no-files-found: ignore
