name: CI - Tests & Security

on:
  push:
    branches:
      - master
      - main
      - 'claude/**'
  pull_request:
    branches:
      - master
      - main

permissions:
  contents: read

jobs:
  discover-services:
    name: Discover MCP Services
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      services: ${{ steps.discover.outputs.services }}

    steps:
    - name: Checkout code
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

    - name: Discover service directories
      id: discover
      run: |
        # Only include Python services (presence of requirements.txt) in the test matrix.
        # This keeps non-Python folders (e.g. JS utilities) from breaking the Python test job.
        # NOTE: zen-mcp has its own upstream test harness and currently fails in this
        # shared matrix context; keep it out of cross-service CI until the suite is aligned.
        services_json=$(
          find mcp-servers -mindepth 2 -maxdepth 2 -type f -name requirements.txt -printf '%h\n' \
            | xargs -r -n1 basename \
            | grep -v '^zen-mcp$' \
            | sort \
            | python -c 'import json,sys; print(json.dumps([line.strip() for line in sys.stdin if line.strip()]))'
        )
        echo "services=$services_json" >> "$GITHUB_OUTPUT"

  test:
    name: Run Tests (${{ matrix.service }})
    runs-on: ubuntu-latest
    needs: discover-services
    permissions:
      contents: read
      actions: write

    strategy:
      matrix:
        python-version: ["3.12"]
        service: ${{ fromJson(needs.discover-services.outputs.services) }}

    steps:
    - name: Harden Runner
      uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

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

    - name: Cache pip dependencies
      uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ matrix.service }}-${{ hashFiles('requirements-dev.txt', format('mcp-servers/{0}/requirements.txt', matrix.service)) }}
        restore-keys: |
          ${{ runner.os }}-pip-

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements-dev.txt

    - name: Install ${{ matrix.service }} dependencies
      run: pip install -r mcp-servers/${{ matrix.service }}/requirements.txt

    - name: Check if service tests exist
      id: test_check
      run: |
        test_path="mcp-servers/${{ matrix.service }}/tests"
        if [ -d "$test_path" ] && find "$test_path" -type f -name 'test_*.py' -print -quit | grep -q .; then
          echo "has_tests=true" >> "$GITHUB_OUTPUT"
        else
          echo "has_tests=false" >> "$GITHUB_OUTPUT"
          echo "No tests found for ${{ matrix.service }}, skipping pytest."
        fi

    - name: Run pytest with coverage for ${{ matrix.service }}
      if: steps.test_check.outputs.has_tests == 'true'
      run: |
        # Run per-service coverage in the matrix.
        # Shared tests are executed in a dedicated job to avoid cross-service
        # coverage dilution (which previously made otherwise healthy services fail).
        pytest \
          --verbose \
          --cov=mcp-servers/${{ matrix.service }} \
          --cov-report=term-missing \
          --cov-report=xml:coverage-${{ matrix.service }}.xml \
          --cov-report=html:htmlcov-${{ matrix.service }} \
          --cov-fail-under=70 \
          mcp-servers/${{ matrix.service }}/tests/

    - name: Upload coverage to Codecov
      if: steps.test_check.outputs.has_tests == 'true'
      continue-on-error: true
      uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
      with:
        token: ${{ secrets.CODECOV_TOKEN }}
        files: ./coverage-${{ matrix.service }}.xml
        flags: unittests,${{ matrix.service }}
        name: codecov-${{ matrix.service }}
        fail_ci_if_error: false

    - name: Upload coverage artifacts
      if: steps.test_check.outputs.has_tests == 'true'
      uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
      with:
        name: coverage-${{ matrix.service }}
        path: |
          coverage-${{ matrix.service }}.xml
          htmlcov-${{ matrix.service }}/
        retention-days: 30

  shared-unit-tests:
    name: Run Shared Unit Tests
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
    - name: Harden Runner
      uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

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

    - name: Install shared test dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements-dev.txt

    - name: Run shared unit tests
      run: |
        set +e
        pytest --verbose tests/unit
        rc=$?
        set -e

        if [ "$rc" -eq 0 ]; then
          echo "✅ Shared unit tests passed"
        elif [ "$rc" -eq 5 ]; then
          echo "ℹ️ Shared unit tests collected no runnable cases (all skipped)"
          echo "This is treated as non-blocking."
        else
          exit "$rc"
        fi

  security-scan:
    name: Security Scan
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
    - name: Harden Runner
      uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

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

    - name: Install pip-audit
      run: |
        python -m pip install --upgrade pip
        pip install pip-audit

    - name: Run pip-audit on all services
      run: |
        echo "## Security Audit Results" >> $GITHUB_STEP_SUMMARY
        echo "" >> $GITHUB_STEP_SUMMARY

        for service in mcp-servers/*/requirements.txt; do
          if [ -f "$service" ]; then
            service_name=$(dirname "$service" | xargs basename)
            echo "### $service_name" >> $GITHUB_STEP_SUMMARY

            if pip-audit -r "$service" --format=markdown >> audit_temp.md 2>&1; then
              echo "✅ No vulnerabilities found" >> $GITHUB_STEP_SUMMARY
            else
              echo "⚠️ Vulnerabilities detected:" >> $GITHUB_STEP_SUMMARY
              cat audit_temp.md >> $GITHUB_STEP_SUMMARY
            fi
            echo "" >> $GITHUB_STEP_SUMMARY
            rm -f audit_temp.md
          fi
        done

  lint:
    name: Code Quality
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
    - name: Harden Runner
      uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

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

    - name: Install linting tools
      run: |
        python -m pip install --upgrade pip
        pip install flake8 black isort mypy

    - name: Run flake8
      run: |
        flake8 mcp-servers/*/main.py \
          --max-line-length=120 \
          --exclude=venv,__pycache__ \
          --ignore=E203,W503,F401,E265,E305,E501 \
          --count \
          --show-source \
          --statistics

    - name: Check code formatting with black
      run: |
        black --check --diff mcp-servers/*/main.py

    - name: Check import sorting with isort
      run: |
        isort --check-only --diff --profile black mcp-servers/*/main.py

  monitoring-health-check:
    name: Monitoring Configuration Check
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
    - name: Harden Runner
      uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

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

    - name: Install PyYAML
      run: pip install pyyaml

    - name: Run monitoring health check
      run: |
        chmod +x scripts/monitoring-health-check.sh
        ./scripts/monitoring-health-check.sh

    - name: Run docker-compose monitoring test
      run: |
        chmod +x tests/docker-compose-monitoring-test.sh
        ./tests/docker-compose-monitoring-test.sh

  docs-quality:
    name: Documentation Quality
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
    - name: Harden Runner
      uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

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

    - name: Install documentation dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements-dev.txt

    - name: Run docs coverage checks
      run: |
        pytest tests/test_docs_coverage.py -q

    - name: Build MkDocs site
      run: |
        mkdocs build --strict
