# Unified CI Pipeline - Mosaic Stack
# Single install, parallel quality gates, sequential deploy
#
# Replaces: api.yml, orchestrator.yml, web.yml
# Keeps: coordinator.yml (Python), infra.yml (separate concerns)
#
# Flow:
#   install → security-audit
#           → prisma-generate → lint + typecheck (parallel)
#                              → prisma-migrate → test
#           → build (after all gates pass)
#           → docker builds (main only, parallel)
#           → trivy scans (main only, parallel)
#           → package linking (main only)

when:
  - event: [push, pull_request, manual]
    path:
      include:
        - "apps/api/**"
        - "apps/orchestrator/**"
        - "apps/web/**"
        - "packages/**"
        - "pnpm-lock.yaml"
        - "pnpm-workspace.yaml"
        - "turbo.json"
        - "package.json"
        - ".woodpecker/ci.yml"
        - ".trivyignore"

variables:
  - &node_image "node:24-slim"
  - &install_deps |
    corepack enable
    apt-get update && apt-get install -y --no-install-recommends python3 make g++
    pnpm config set store-dir /root/.local/share/pnpm/store
    pnpm install --frozen-lockfile
  - &use_deps |
    corepack enable
  - &turbo_env
    TURBO_API:
      from_secret: turbo_api
    TURBO_TOKEN:
      from_secret: turbo_token
    TURBO_TEAM:
      from_secret: turbo_team
  - &kaniko_setup |
    mkdir -p /kaniko/.docker
    echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json

services:
  postgres:
    image: postgres:17.7-alpine3.22
    environment:
      POSTGRES_DB: test_db
      POSTGRES_USER: test_user
      POSTGRES_PASSWORD: test_password

steps:
  # ─── Install (once) ─────────────────────────────────────────
  install:
    image: *node_image
    commands:
      - *install_deps

  # ─── Security Audit (once) ──────────────────────────────────
  security-audit:
    image: *node_image
    commands:
      - *use_deps
      - pnpm audit --audit-level=high
    depends_on:
      - install

  # ─── Prisma Generate ────────────────────────────────────────
  prisma-generate:
    image: *node_image
    environment:
      SKIP_ENV_VALIDATION: "true"
    commands:
      - *use_deps
      - pnpm --filter "@mosaic/api" prisma:generate
    depends_on:
      - install

  # ─── Lint (all packages) ────────────────────────────────────
  lint:
    image: *node_image
    environment:
      SKIP_ENV_VALIDATION: "true"
      <<: *turbo_env
    commands:
      - *use_deps
      - pnpm turbo lint
    depends_on:
      - prisma-generate

  # ─── Typecheck (all packages, parallel with lint) ───────────
  typecheck:
    image: *node_image
    environment:
      SKIP_ENV_VALIDATION: "true"
      <<: *turbo_env
    commands:
      - *use_deps
      - pnpm turbo typecheck
    depends_on:
      - prisma-generate

  # ─── Prisma Migrate (test DB) ──────────────────────────────
  prisma-migrate:
    image: *node_image
    environment:
      SKIP_ENV_VALIDATION: "true"
      DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db?schema=public"
    commands:
      - *use_deps
      - pnpm --filter "@mosaic/api" prisma migrate deploy
    depends_on:
      - prisma-generate

  # ─── Test (all packages) ───────────────────────────────────
  test:
    image: *node_image
    environment:
      SKIP_ENV_VALIDATION: "true"
      DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db?schema=public"
      ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
      <<: *turbo_env
    commands:
      - *use_deps
      - pnpm --filter "@mosaic/api" exec vitest run --exclude 'src/auth/auth-rls.integration.spec.ts' --exclude 'src/credentials/user-credential.model.spec.ts' --exclude 'src/job-events/job-events.performance.spec.ts' --exclude 'src/knowledge/services/fulltext-search.spec.ts' --exclude 'src/mosaic-telemetry/mosaic-telemetry.module.spec.ts'
      - pnpm turbo test --filter=@mosaic/orchestrator --filter=@mosaic/web
    depends_on:
      - prisma-migrate

  # ─── Build (all packages) ──────────────────────────────────
  build:
    image: *node_image
    environment:
      SKIP_ENV_VALIDATION: "true"
      NODE_ENV: "production"
      <<: *turbo_env
    commands:
      - *use_deps
      - pnpm turbo build
    depends_on:
      - lint
      - typecheck
      - test
      - security-audit

  # ─── Docker Builds (main only, parallel) ───────────────────

  docker-build-api:
    image: gcr.io/kaniko-project/executor:debug
    environment:
      GITEA_USER:
        from_secret: gitea_username
      GITEA_TOKEN:
        from_secret: gitea_token
      CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
      CI_COMMIT_TAG: ${CI_COMMIT_TAG}
    commands:
      - *kaniko_setup
      - |
        DESTINATIONS=""
        if [ -n "$CI_COMMIT_TAG" ]; then
          DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-api:$CI_COMMIT_TAG"
        elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
          DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-api:latest"
        fi
        /kaniko/executor --context . --dockerfile apps/api/Dockerfile --snapshot-mode=redo --cache=true --cache-repo git.mosaicstack.dev/mosaic/stack-api/cache $DESTINATIONS
    when:
      - branch: [main]
        event: [push, manual, tag]
    depends_on:
      - build

  docker-build-orchestrator:
    image: gcr.io/kaniko-project/executor:debug
    environment:
      GITEA_USER:
        from_secret: gitea_username
      GITEA_TOKEN:
        from_secret: gitea_token
      CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
      CI_COMMIT_TAG: ${CI_COMMIT_TAG}
    commands:
      - *kaniko_setup
      - |
        DESTINATIONS=""
        if [ -n "$CI_COMMIT_TAG" ]; then
          DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-orchestrator:$CI_COMMIT_TAG"
        elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
          DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-orchestrator:latest"
        fi
        /kaniko/executor --context . --dockerfile apps/orchestrator/Dockerfile --snapshot-mode=redo --cache=true --cache-repo git.mosaicstack.dev/mosaic/stack-orchestrator/cache $DESTINATIONS
    when:
      - branch: [main]
        event: [push, manual, tag]
    depends_on:
      - build

  docker-build-web:
    image: gcr.io/kaniko-project/executor:debug
    environment:
      GITEA_USER:
        from_secret: gitea_username
      GITEA_TOKEN:
        from_secret: gitea_token
      CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
      CI_COMMIT_TAG: ${CI_COMMIT_TAG}
    commands:
      - *kaniko_setup
      - |
        DESTINATIONS=""
        if [ -n "$CI_COMMIT_TAG" ]; then
          DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-web:$CI_COMMIT_TAG"
        elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
          DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-web:latest"
        fi
        /kaniko/executor --context . --dockerfile apps/web/Dockerfile --snapshot-mode=redo --cache=true --cache-repo git.mosaicstack.dev/mosaic/stack-web/cache --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev $DESTINATIONS
    when:
      - branch: [main]
        event: [push, manual, tag]
    depends_on:
      - build

  # ─── Container Security Scans (main only) ──────────────────

  security-trivy-api:
    image: aquasec/trivy:latest
    environment:
      GITEA_USER:
        from_secret: gitea_username
      GITEA_TOKEN:
        from_secret: gitea_token
      CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
      CI_COMMIT_TAG: ${CI_COMMIT_TAG}
    commands:
      - |
        if [ -n "$$CI_COMMIT_TAG" ]; then SCAN_TAG="$$CI_COMMIT_TAG"; else SCAN_TAG="latest"; fi
        mkdir -p ~/.docker
        echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
        trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --ignorefile .trivyignore git.mosaicstack.dev/mosaic/stack-api:$$SCAN_TAG
    when:
      - branch: [main]
        event: [push, manual, tag]
    depends_on:
      - docker-build-api

  security-trivy-orchestrator:
    image: aquasec/trivy:latest
    environment:
      GITEA_USER:
        from_secret: gitea_username
      GITEA_TOKEN:
        from_secret: gitea_token
      CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
      CI_COMMIT_TAG: ${CI_COMMIT_TAG}
    commands:
      - |
        if [ -n "$$CI_COMMIT_TAG" ]; then SCAN_TAG="$$CI_COMMIT_TAG"; else SCAN_TAG="latest"; fi
        mkdir -p ~/.docker
        echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
        trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --ignorefile .trivyignore git.mosaicstack.dev/mosaic/stack-orchestrator:$$SCAN_TAG
    when:
      - branch: [main]
        event: [push, manual, tag]
    depends_on:
      - docker-build-orchestrator

  security-trivy-web:
    image: aquasec/trivy:latest
    environment:
      GITEA_USER:
        from_secret: gitea_username
      GITEA_TOKEN:
        from_secret: gitea_token
      CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
      CI_COMMIT_TAG: ${CI_COMMIT_TAG}
    commands:
      - |
        if [ -n "$$CI_COMMIT_TAG" ]; then SCAN_TAG="$$CI_COMMIT_TAG"; else SCAN_TAG="latest"; fi
        mkdir -p ~/.docker
        echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
        trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --ignorefile .trivyignore git.mosaicstack.dev/mosaic/stack-web:$$SCAN_TAG
    when:
      - branch: [main]
        event: [push, manual, tag]
    depends_on:
      - docker-build-web

  # ─── Package Linking (main only, once) ─────────────────────

  link-packages:
    image: alpine:3
    environment:
      GITEA_TOKEN:
        from_secret: gitea_token
    commands:
      - apk add --no-cache curl
      - sleep 10
      - |
        set -e
        link_package() {
          PKG="$$1"
          echo "Linking $$PKG..."
          for attempt in 1 2 3; do
            STATUS=$$(curl -s -o /tmp/link-response.txt -w "%{http_code}" -X POST \
              -H "Authorization: token $$GITEA_TOKEN" \
              "https://git.mosaicstack.dev/api/v1/packages/mosaic/container/$$PKG/-/link/stack")
            if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ]; then
              echo "  Linked $$PKG"
              return 0
            elif [ "$$STATUS" = "400" ]; then
              echo "  $$PKG already linked"
              return 0
            elif [ "$$STATUS" = "404" ] && [ $$attempt -lt 3 ]; then
              echo "  $$PKG not found yet, retrying in 5s (attempt $$attempt/3)..."
              sleep 5
            else
              echo "  FAILED: $$PKG status $$STATUS"
              cat /tmp/link-response.txt
              return 1
            fi
          done
        }
        link_package "stack-api"
        link_package "stack-orchestrator"
        link_package "stack-web"
    when:
      - branch: [main]
        event: [push, manual, tag]
    depends_on:
      - security-trivy-api
      - security-trivy-orchestrator
      - security-trivy-web

  # ─── Deploy to Docker Swarm (main only) ─────────────────────

  # ─── Deploy to Docker Swarm via Portainer (main only) ─────────────────────

  deploy-swarm:
    image: alpine:3
    environment:
      SSH_PRIVATE_KEY:
        from_secret: ssh_private_key
      SSH_KNOWN_HOSTS:
        from_secret: ssh_known_hosts
      PORTAINER_URL:
        from_secret: portainer_url
      PORTAINER_API_KEY:
        from_secret: portainer_api_key
    commands:
      - apk add --no-cache curl openssh-client
      - |
        set -e
        echo "🚀 Deploying to Docker Swarm..."

        # Setup SSH for fallback
        mkdir -p ~/.ssh
        echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
        chmod 600 ~/.ssh/known_hosts
        echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
        chmod 600 ~/.ssh/id_ed25519

        # Force service updates (images are pulled from public registry)
        ssh -o StrictHostKeyChecking=no localadmin@10.1.1.45 \
          "docker service update --with-registry-auth --force mosaic-stack-api && \
           docker service update --with-registry-auth --force mosaic-stack-web && \
           docker service update --with-registry-auth --force mosaic-stack-orchestrator && \
           docker service update --with-registry-auth --force mosaic-stack-coordinator && \
           echo '✅ All services updated'"
    when:
      - branch: [main]
        event: [push, manual, tag]
    depends_on:
      - link-packages
