# ─────────────────────────────────────────────────────────────────────────────
# GitLab CI/CD Pipeline – Claude Code Agent Monitor
#
# Stages: test → build → deploy-staging → deploy-production
# ─────────────────────────────────────────────────────────────────────────────

# ── Global settings ─────────────────────────────────────────────────────────
default:
  image: node:22-alpine
  interruptible: true
  retry:
    max: 1
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

variables:
  NODE_VERSION: "22"
  REGISTRY: "${CI_REGISTRY}"
  IMAGE_NAME: "${CI_REGISTRY_IMAGE}/agent-monitor"
  MCP_IMAGE_NAME: "${CI_REGISTRY_IMAGE}/agent-monitor-mcp"
  HELM_CHART_PATH: "deployments/helm/agent-monitor"
  APP_NAME: "agent-monitor"
  # Kaniko cache
  KANIKO_CACHE_ARGS: "--cache=true --cache-repo=${CI_REGISTRY_IMAGE}/cache"

stages:
  - test
  - build
  - deploy-staging
  - deploy-production
  - rollback

# ── Cache configuration ────────────────────────────────────────────────────
.node_cache: &node_cache
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
    policy: pull-push

# ── Test stage ──────────────────────────────────────────────────────────────
lint:
  stage: test
  <<: *node_cache
  script:
    - npm ci --prefer-offline
    - npm run format:check
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

test:server:
  stage: test
  <<: *node_cache
  script:
    - npm ci --prefer-offline
    - npm run test:server
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  artifacts:
    when: on_failure
    paths:
      - server/__tests__/
    expire_in: 7 days

test:client:
  stage: test
  <<: *node_cache
  script:
    - npm ci --prefer-offline
    - cd client && npm ci --prefer-offline
    - npm run test:client
  cache:
    key:
      files:
        - client/package-lock.json
    paths:
      - client/node_modules/
    policy: pull-push
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

test:mcp:
  stage: test
  <<: *node_cache
  script:
    - npm run mcp:install
    - npm run mcp:typecheck
    - npm run mcp:test
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# ── Build stage ─────────────────────────────────────────────────────────────
.kaniko_build: &kaniko_build
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:v1.22.0-debug
    entrypoint: [""]
  before_script:
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64)\"}}}" > /kaniko/.docker/config.json

build:app:
  <<: *kaniko_build
  script:
    - >-
      /kaniko/executor
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/Dockerfile"
      --destination "${IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
      --destination "${IMAGE_NAME}:${CI_COMMIT_REF_SLUG}"
      --destination "${IMAGE_NAME}:latest"
      ${KANIKO_CACHE_ARGS}
      --label "org.opencontainers.image.revision=${CI_COMMIT_SHA}"
      --label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
      --label "org.opencontainers.image.source=${CI_PROJECT_URL}"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/

build:mcp:
  <<: *kaniko_build
  script:
    - >-
      /kaniko/executor
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/mcp/Dockerfile"
      --destination "${MCP_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
      --destination "${MCP_IMAGE_NAME}:${CI_COMMIT_REF_SLUG}"
      --destination "${MCP_IMAGE_NAME}:latest"
      ${KANIKO_CACHE_ARGS}
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/

# Security scan
security:scan:
  stage: build
  needs: ["build:app", "build:mcp"]
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL --format table "${IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
    - trivy image --exit-code 1 --severity HIGH,CRITICAL --format table "${MCP_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
    - trivy image --exit-code 1 --severity CRITICAL --format json --output trivy-app-report.json "${IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
    - trivy image --exit-code 1 --severity CRITICAL --format json --output trivy-mcp-report.json "${MCP_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
  artifacts:
    paths:
      - trivy-app-report.json
      - trivy-mcp-report.json
    expire_in: 30 days
  allow_failure: false
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/

# ── Deploy Staging ──────────────────────────────────────────────────────────
deploy:staging:
  stage: deploy-staging
  image:
    name: alpine/helm:3.14.0
    entrypoint: [""]
  needs:
    - build:app
    - build:mcp
  environment:
    name: staging
    url: https://staging.agent-monitor.example.com
    on_stop: stop:staging
  before_script:
    - apk add --no-cache curl aws-cli kubectl
    - aws eks update-kubeconfig --region "${AWS_REGION:-us-west-2}" --name "${EKS_CLUSTER_STAGING:-agent-monitor-staging}"
  script:
    - |
      helm upgrade --install ${APP_NAME} ${HELM_CHART_PATH} \
        --namespace agent-monitor-staging \
        --create-namespace \
        --set image.repository=${IMAGE_NAME} \
        --set image.tag=${CI_COMMIT_SHORT_SHA} \
        --set mcp.image.repository=${MCP_IMAGE_NAME} \
        --set mcp.image.tag=${CI_COMMIT_SHORT_SHA} \
        --set environment=staging \
        --values ${HELM_CHART_PATH}/values-staging.yaml \
        --wait \
        --atomic \
        --timeout 600s
    - |
      echo "Running health check..."
      kubectl wait --for=condition=ready pod \
        -l app.kubernetes.io/name=${APP_NAME} \
        -n agent-monitor-staging \
        --timeout=300s
      echo "✔ Staging deployment successful"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/

stop:staging:
  stage: deploy-staging
  image:
    name: alpine/helm:3.14.0
    entrypoint: [""]
  environment:
    name: staging
    action: stop
  before_script:
    - apk add --no-cache aws-cli kubectl
    - aws eks update-kubeconfig --region "${AWS_REGION:-us-west-2}" --name "${EKS_CLUSTER_STAGING:-agent-monitor-staging}"
  script:
    - helm uninstall ${APP_NAME} -n agent-monitor-staging --wait || true
  when: manual
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# ── Deploy Production ───────────────────────────────────────────────────────
deploy:production:
  stage: deploy-production
  image:
    name: alpine/helm:3.14.0
    entrypoint: [""]
  needs:
    - deploy:staging
  environment:
    name: production
    url: https://agent-monitor.example.com
  before_script:
    - apk add --no-cache curl aws-cli kubectl
    - aws eks update-kubeconfig --region "${AWS_REGION:-us-west-2}" --name "${EKS_CLUSTER_PRODUCTION:-agent-monitor-production}"
  script:
    # Pre-deploy backup
    - |
      POD=$(kubectl get pods -n agent-monitor-production \
        -l app.kubernetes.io/name=${APP_NAME} \
        --field-selector=status.phase=Running \
        -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
      if [ -n "$POD" ]; then
        echo "Creating pre-deploy backup..."
        kubectl exec "$POD" -n agent-monitor-production -- \
          sh -c "cp /app/data/dashboard.db /tmp/pre-deploy-backup.db" 2>/dev/null || true
      fi
    # Deploy
    - |
      helm upgrade --install ${APP_NAME} ${HELM_CHART_PATH} \
        --namespace agent-monitor-production \
        --create-namespace \
        --set image.repository=${IMAGE_NAME} \
        --set image.tag=${CI_COMMIT_SHORT_SHA} \
        --set mcp.image.repository=${MCP_IMAGE_NAME} \
        --set mcp.image.tag=${CI_COMMIT_SHORT_SHA} \
        --set environment=production \
        --values ${HELM_CHART_PATH}/values-production.yaml \
        --wait \
        --atomic \
        --timeout 600s
    # Health check
    - |
      echo "Running production health check..."
      kubectl wait --for=condition=ready pod \
        -l app.kubernetes.io/name=${APP_NAME} \
        -n agent-monitor-production \
        --timeout=300s
      echo "✔ Production deployment successful"
  when: manual
  allow_failure: false
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/

# ── Rollback ────────────────────────────────────────────────────────────────
rollback:staging:
  stage: rollback
  image:
    name: alpine/helm:3.14.0
    entrypoint: [""]
  environment:
    name: staging
  before_script:
    - apk add --no-cache aws-cli kubectl
    - aws eks update-kubeconfig --region "${AWS_REGION:-us-west-2}" --name "${EKS_CLUSTER_STAGING:-agent-monitor-staging}"
  script:
    - echo "Rolling back staging..."
    - helm rollback ${APP_NAME} ${ROLLBACK_REVISION:-0} -n agent-monitor-staging --wait --timeout 300s
    - |
      kubectl wait --for=condition=ready pod \
        -l app.kubernetes.io/name=${APP_NAME} \
        -n agent-monitor-staging \
        --timeout=300s
    - echo "✔ Staging rollback complete"
  when: manual
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

rollback:production:
  stage: rollback
  image:
    name: alpine/helm:3.14.0
    entrypoint: [""]
  environment:
    name: production
  before_script:
    - apk add --no-cache aws-cli kubectl
    - aws eks update-kubeconfig --region "${AWS_REGION:-us-west-2}" --name "${EKS_CLUSTER_PRODUCTION:-agent-monitor-production}"
  script:
    - echo "⚠ Rolling back PRODUCTION..."
    - helm history ${APP_NAME} -n agent-monitor-production --max 5
    - helm rollback ${APP_NAME} ${ROLLBACK_REVISION:-0} -n agent-monitor-production --wait --timeout 300s
    - |
      kubectl wait --for=condition=ready pod \
        -l app.kubernetes.io/name=${APP_NAME} \
        -n agent-monitor-production \
        --timeout=300s
    - echo "✔ Production rollback complete"
  when: manual
  allow_failure: false
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
