# Woodpecker CI Main Pipeline
# BreakPilot Lehrer - CI/CD Pipeline
#
# Plattform: ARM64 (Apple Silicon Mac Mini)
#
# Services:
#   Go: school-service, edu-search-service
#   Python: klausur-service, backend-lehrer, agent-core
#   Node.js: website, admin-lehrer, studio-v2
#
# Strategie:
# - Lint bei PRs
# - Tests laufen bei JEDEM Push/PR
# - Test-Ergebnisse werden an Dashboard gesendet
# - Builds/Scans laufen nur bei Tags oder manuell
# - Deployment nur manuell (Sicherheit)

when:
  - event: [push, pull_request, manual, tag]
    branch: [main, develop]

clone:
  git:
    image: woodpeckerci/plugin-git
    settings:
      depth: 1
    extra_hosts:
      - macmini:192.168.178.100

variables:
  - &golang_image golang:1.23-alpine
  - &python_image python:3.12-slim
  - &nodejs_image node:20-alpine
  - &docker_image docker:27-cli

steps:
  # ========================================
  # STAGE 1: Lint (nur bei PRs)
  # ========================================

  go-lint:
    image: golangci/golangci-lint:v1.55-alpine
    commands:
      - |
        for svc in school-service edu-search-service; do
          if [ -d "$svc" ]; then
            echo "=== Linting $svc ==="
            cd "$svc" && golangci-lint run --timeout 5m ./... || true
            cd ..
          fi
        done
    when:
      event: pull_request

  python-lint:
    image: *python_image
    commands:
      - pip install --quiet ruff
      - |
        for svc in backend-lehrer agent-core; do
          if [ -d "$svc" ]; then
            echo "=== Linting $svc ==="
            ruff check "$svc/" --output-format=github || true
          fi
        done
        if [ -d "klausur-service/backend" ]; then
          echo "=== Linting klausur-service ==="
          ruff check klausur-service/backend/ --output-format=github || true
        fi
    when:
      event: pull_request

  nodejs-lint:
    image: *nodejs_image
    commands:
      - |
        for svc in website admin-lehrer studio-v2; do
          if [ -d "$svc" ]; then
            echo "=== Linting $svc ==="
            cd "$svc"
            npm ci --silent 2>/dev/null || npm install --silent
            npx next lint || true
            cd ..
          fi
        done
    when:
      event: pull_request

  # ========================================
  # STAGE 2: Unit Tests mit JSON-Ausgabe
  # Ergebnisse werden im Workspace gespeichert (.ci-results/)
  # ========================================

  test-go-school:
    image: *golang_image
    environment:
      CGO_ENABLED: "0"
    commands:
      - |
        set -euo pipefail
        apk add --no-cache jq bash
        mkdir -p .ci-results

        if [ ! -d "school-service" ]; then
          echo '{"service":"school-service","framework":"go","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-school.json
          echo "WARNUNG: school-service Verzeichnis nicht gefunden"
          exit 0
        fi

        cd school-service
        set +e
        go test -v -json -coverprofile=coverage.out ./... 2>&1 | tee ../.ci-results/test-school.json
        TEST_EXIT=$?
        set -e

        JSON_FILE="../.ci-results/test-school.json"
        if grep -q '^{' "$JSON_FILE" 2>/dev/null; then
          TOTAL=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="run" and .Test != null)] | length')
          PASSED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="pass" and .Test != null)] | length')
          FAILED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="fail" and .Test != null)] | length')
          SKIPPED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="skip" and .Test != null)] | length')
        else
          echo "WARNUNG: Keine JSON-Zeilen in $JSON_FILE gefunden (Build-Fehler?)"
          TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0
        fi

        COVERAGE=$(go tool cover -func=coverage.out 2>/dev/null | tail -1 | awk '{print $3}' | tr -d '%' || echo "0")
        [ -z "$COVERAGE" ] && COVERAGE=0

        echo "{\"service\":\"school-service\",\"framework\":\"go\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":$COVERAGE}" > ../.ci-results/results-school.json
        cat ../.ci-results/results-school.json

        if [ "$FAILED" -gt "0" ]; then
          echo "WARNUNG: $FAILED Tests fehlgeschlagen - werden ins Backlog geschrieben"
        fi

  test-go-edu-search:
    image: *golang_image
    environment:
      CGO_ENABLED: "0"
    commands:
      - |
        set -euo pipefail
        apk add --no-cache jq bash
        mkdir -p .ci-results

        if [ ! -d "edu-search-service" ]; then
          echo '{"service":"edu-search-service","framework":"go","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-edu-search.json
          echo "WARNUNG: edu-search-service Verzeichnis nicht gefunden"
          exit 0
        fi

        cd edu-search-service
        go mod download
        set +e
        go test -v -json ./... 2>&1 | tee ../.ci-results/test-edu-search.json
        TEST_EXIT=$?
        set -e

        JSON_FILE="../.ci-results/test-edu-search.json"
        if grep -q '^{' "$JSON_FILE" 2>/dev/null; then
          TOTAL=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="run" and .Test != null)] | length')
          PASSED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="pass" and .Test != null)] | length')
          FAILED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="fail" and .Test != null)] | length')
          SKIPPED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="skip" and .Test != null)] | length')
        else
          echo "WARNUNG: Keine JSON-Zeilen in $JSON_FILE gefunden (Build-Fehler?)"
          TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0
        fi

        echo "{\"service\":\"edu-search-service\",\"framework\":\"go\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-edu-search.json
        cat ../.ci-results/results-edu-search.json

        if [ "$FAILED" -gt "0" ]; then
          echo "WARNUNG: $FAILED Tests fehlgeschlagen"
        fi

  test-python-klausur:
    image: *python_image
    environment:
      CI: "true"
    commands:
      - |
        set -uo pipefail
        mkdir -p .ci-results

        if [ ! -d "klausur-service/backend" ]; then
          echo '{"service":"klausur-service","framework":"pytest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-klausur.json
          echo "WARNUNG: klausur-service/backend Verzeichnis nicht gefunden"
          exit 0
        fi

        cd klausur-service/backend
        export PYTHONPATH="$(pwd):${PYTHONPATH:-}"

        pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true
        pip install --quiet --no-cache-dir fastapi uvicorn pytest pytest-asyncio pytest-json-report

        set +e
        python -m pytest tests/ -v --tb=short --json-report --json-report-file=../../.ci-results/test-klausur.json
        TEST_EXIT=$?
        set -e

        if [ -f ../../.ci-results/test-klausur.json ]; then
          TOTAL=$(python3 -c "import json; d=json.load(open('../../.ci-results/test-klausur.json')); print(d.get('summary',{}).get('total',0))" 2>/dev/null || echo "0")
          PASSED=$(python3 -c "import json; d=json.load(open('../../.ci-results/test-klausur.json')); print(d.get('summary',{}).get('passed',0))" 2>/dev/null || echo "0")
          FAILED=$(python3 -c "import json; d=json.load(open('../../.ci-results/test-klausur.json')); print(d.get('summary',{}).get('failed',0))" 2>/dev/null || echo "0")
          SKIPPED=$(python3 -c "import json; d=json.load(open('../../.ci-results/test-klausur.json')); print(d.get('summary',{}).get('skipped',0))" 2>/dev/null || echo "0")
        else
          TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0
        fi

        echo "{\"service\":\"klausur-service\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../../.ci-results/results-klausur.json
        cat ../../.ci-results/results-klausur.json

        if [ "$TEST_EXIT" -ne "0" ]; then exit 1; fi
  test-python-agent-core:
    image: *python_image
    environment:
      CI: "true"
    commands:
      - |
        set -uo pipefail
        mkdir -p .ci-results

        if [ ! -d "agent-core" ]; then
          echo '{"service":"agent-core","framework":"pytest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-agent-core.json
          echo "WARNUNG: agent-core Verzeichnis nicht gefunden"
          exit 0
        fi

        # Symlink erstellen damit 'import agent_core' funktioniert
        # (Verzeichnis heisst agent-core mit Bindestrich, Python braucht Unterstrich)
        ln -sf agent-core agent_core
        export PYTHONPATH="$(pwd):${PYTHONPATH:-}"

        cd agent-core
        pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true
        pip install --quiet --no-cache-dir pytest pytest-asyncio pytest-cov pytest-json-report

        set +e
        python -m pytest tests/ -v --tb=short --json-report --json-report-file=../.ci-results/test-agent-core.json
        TEST_EXIT=$?
        set -e

        if [ -f ../.ci-results/test-agent-core.json ]; then
          TOTAL=$(python3 -c "import json; d=json.load(open('../.ci-results/test-agent-core.json')); print(d.get('summary',{}).get('total',0))" 2>/dev/null || echo "0")
          PASSED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-agent-core.json')); print(d.get('summary',{}).get('passed',0))" 2>/dev/null || echo "0")
          FAILED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-agent-core.json')); print(d.get('summary',{}).get('failed',0))" 2>/dev/null || echo "0")
          SKIPPED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-agent-core.json')); print(d.get('summary',{}).get('skipped',0))" 2>/dev/null || echo "0")
        else
          TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0
        fi

        echo "{\"service\":\"agent-core\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-agent-core.json
        cat ../.ci-results/results-agent-core.json

        if [ "$TEST_EXIT" -ne "0" ]; then exit 1; fi

  test-nodejs-website:
    image: *nodejs_image
    commands:
      - |
        set -uo pipefail
        mkdir -p .ci-results

        if [ ! -d "website" ]; then
          echo '{"service":"website","framework":"jest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-website.json
          echo "WARNUNG: website Verzeichnis nicht gefunden"
          exit 0
        fi

        cd website
        npm ci --silent 2>/dev/null || npm install --silent

        set +e
        npx jest --json --outputFile=../.ci-results/test-website.json 2>&1
        TEST_EXIT=$?
        set -e

        if [ -f ../.ci-results/test-website.json ]; then
          TOTAL=$(node -e "const d=require('../.ci-results/test-website.json'); console.log(d.numTotalTests || 0)" 2>/dev/null || echo "0")
          PASSED=$(node -e "const d=require('../.ci-results/test-website.json'); console.log(d.numPassedTests || 0)" 2>/dev/null || echo "0")
          FAILED=$(node -e "const d=require('../.ci-results/test-website.json'); console.log(d.numFailedTests || 0)" 2>/dev/null || echo "0")
          SKIPPED=$(node -e "const d=require('../.ci-results/test-website.json'); console.log(d.numPendingTests || 0)" 2>/dev/null || echo "0")
        else
          TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0
        fi

        [ -z "$TOTAL" ] && TOTAL=0
        [ -z "$PASSED" ] && PASSED=0
        [ -z "$FAILED" ] && FAILED=0
        [ -z "$SKIPPED" ] && SKIPPED=0

        echo "{\"service\":\"website\",\"framework\":\"jest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-website.json
        cat ../.ci-results/results-website.json

        if [ "$TEST_EXIT" -ne "0" ]; then exit 1; fi

  # ========================================
  # STAGE 3: Test-Ergebnisse an Dashboard senden
  # ========================================

  report-test-results:
    image: curlimages/curl:8.10.1
    commands:
      - |
        set -uo pipefail
        echo "=== Sende Test-Ergebnisse an Dashboard ==="
        echo "Pipeline Status: ${CI_PIPELINE_STATUS:-unknown}"
        ls -la .ci-results/ || echo "Verzeichnis nicht gefunden"

        PIPELINE_STATUS="${CI_PIPELINE_STATUS:-unknown}"

        for f in .ci-results/results-*.json; do
          [ -f "$f" ] || continue
          echo "Sending: $f"
          curl -f -sS -X POST "http://backend:8000/api/tests/ci-result" \
            -H "Content-Type: application/json" \
            -d "{
              \"pipeline_id\": \"${CI_PIPELINE_NUMBER}\",
              \"commit\": \"${CI_COMMIT_SHA}\",
              \"branch\": \"${CI_COMMIT_BRANCH}\",
              \"repo\": \"breakpilot-lehrer\",
              \"status\": \"${PIPELINE_STATUS}\",
              \"test_results\": $(cat "$f")
            }" || echo "WARNUNG: Konnte $f nicht senden"
        done

        echo "=== Test-Ergebnisse gesendet ==="
    when:
      status: [success, failure]
    depends_on:
      - test-go-school
      - test-go-edu-search
      - test-python-klausur
      - test-python-agent-core
      - test-nodejs-website

  # ========================================
  # STAGE 4: Build & Security (nur Tags/manuell)
  # ========================================

  build-admin-lehrer:
    image: *docker_image
    commands:
      - |
        if [ -d ./admin-lehrer ]; then
          docker build -t breakpilot/admin-lehrer:${CI_COMMIT_SHA:0:8} ./admin-lehrer
          docker tag breakpilot/admin-lehrer:${CI_COMMIT_SHA:0:8} breakpilot/admin-lehrer:latest
          echo "Built breakpilot/admin-lehrer:${CI_COMMIT_SHA:0:8}"
        else
          echo "admin-lehrer Verzeichnis nicht gefunden - ueberspringe"
        fi
    when:
      - event: tag
      - event: manual

  build-studio-v2:
    image: *docker_image
    commands:
      - |
        if [ -d ./studio-v2 ]; then
          docker build -t breakpilot/studio-v2:${CI_COMMIT_SHA:0:8} ./studio-v2
          docker tag breakpilot/studio-v2:${CI_COMMIT_SHA:0:8} breakpilot/studio-v2:latest
          echo "Built breakpilot/studio-v2:${CI_COMMIT_SHA:0:8}"
        else
          echo "studio-v2 Verzeichnis nicht gefunden - ueberspringe"
        fi
    when:
      - event: tag
      - event: manual

  build-website:
    image: *docker_image
    commands:
      - |
        if [ -d ./website ]; then
          docker build -t breakpilot/website:${CI_COMMIT_SHA:0:8} ./website
          docker tag breakpilot/website:${CI_COMMIT_SHA:0:8} breakpilot/website:latest
          echo "Built breakpilot/website:${CI_COMMIT_SHA:0:8}"
        else
          echo "website Verzeichnis nicht gefunden - ueberspringe"
        fi
    when:
      - event: tag
      - event: manual

  build-backend-lehrer:
    image: *docker_image
    commands:
      - |
        if [ -d ./backend-lehrer ]; then
          docker build -t breakpilot/backend-lehrer:${CI_COMMIT_SHA:0:8} ./backend-lehrer
          docker tag breakpilot/backend-lehrer:${CI_COMMIT_SHA:0:8} breakpilot/backend-lehrer:latest
          echo "Built breakpilot/backend-lehrer:${CI_COMMIT_SHA:0:8}"
        else
          echo "backend-lehrer Verzeichnis nicht gefunden - ueberspringe"
        fi
    when:
      - event: tag
      - event: manual

  build-klausur-service:
    image: *docker_image
    commands:
      - |
        if [ -d ./klausur-service ]; then
          docker build -t breakpilot/klausur-service:${CI_COMMIT_SHA:0:8} ./klausur-service
          docker tag breakpilot/klausur-service:${CI_COMMIT_SHA:0:8} breakpilot/klausur-service:latest
          echo "Built breakpilot/klausur-service:${CI_COMMIT_SHA:0:8}"
        else
          echo "klausur-service Verzeichnis nicht gefunden - ueberspringe"
        fi
    when:
      - event: tag
      - event: manual

  build-school-service:
    image: *docker_image
    commands:
      - |
        if [ -d ./school-service ]; then
          docker build -t breakpilot/school-service:${CI_COMMIT_SHA:0:8} ./school-service
          docker tag breakpilot/school-service:${CI_COMMIT_SHA:0:8} breakpilot/school-service:latest
          echo "Built breakpilot/school-service:${CI_COMMIT_SHA:0:8}"
        else
          echo "school-service Verzeichnis nicht gefunden - ueberspringe"
        fi
    when:
      - event: tag
      - event: manual

  build-edu-search-service:
    image: *docker_image
    commands:
      - |
        if [ -d ./edu-search-service ]; then
          docker build -t breakpilot/edu-search-service:${CI_COMMIT_SHA:0:8} ./edu-search-service
          docker tag breakpilot/edu-search-service:${CI_COMMIT_SHA:0:8} breakpilot/edu-search-service:latest
          echo "Built breakpilot/edu-search-service:${CI_COMMIT_SHA:0:8}"
        else
          echo "edu-search-service Verzeichnis nicht gefunden - ueberspringe"
        fi
    when:
      - event: tag
      - event: manual

  generate-sbom:
    image: python:3.12-slim
    commands:
      - |
        echo "Installing syft for ARM64..."
        apt-get update -qq && apt-get install -y -qq wget > /dev/null
        wget -qO- https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
        for svc in klausur-service backend-lehrer website school-service edu-search-service agent-core; do
          if [ -d "./$svc" ]; then
            syft dir:./$svc -o cyclonedx-json > sbom-$svc.json
            echo "SBOM generated for $svc"
          fi
        done
    when:
      - event: tag
      - event: manual

  vulnerability-scan:
    image: python:3.12-slim
    commands:
      - |
        echo "Installing grype for ARM64..."
        apt-get update -qq && apt-get install -y -qq wget > /dev/null
        wget -qO- https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
        for f in sbom-*.json; do
          [ -f "$f" ] || continue
          echo "=== Scanning $f ==="
          grype sbom:"$f" -o table --fail-on critical || true
        done
    when:
      - event: tag
      - event: manual
    depends_on:
      - generate-sbom

  # ========================================
  # STAGE 5: Deploy (nur manuell)
  # ========================================

  deploy-production:
    image: *docker_image
    commands:
      - echo "Deploying breakpilot-lehrer to production..."
      - docker compose -f docker-compose.yml pull || true
      - docker compose -f docker-compose.yml up -d --remove-orphans || true
    when:
      event: manual
    depends_on:
      - build-admin-lehrer
      - build-studio-v2
      - build-website
      - build-backend-lehrer
      - build-klausur-service
      - build-school-service
      - build-edu-search-service
