# =============================================================================
# GitLab CI/CD Pipeline — STEAM Security Dashboard
# =============================================================================
# Executor: Docker (LXC 108 — 71.85.90.8)
# Build/test jobs run in node:18 containers.
# Release: v2.1.0
# Deploy jobs run in alpine with SSH/rsync, targeting staging (71.85.90.9)
# and production (71.85.90.6) via SSH.
# =============================================================================

variables:
  PROD_HOST: "71.85.90.6"
  PROD_USER: "root"
  PROD_DIR: "/home/cve-dashboard"
  STAGING_HOST: "71.85.90.9"
  STAGING_USER: "root"
  STAGING_DIR: "/home/cve-dashboard-staging"

stages:
  - install
  - lint
  - test
  - build
  - deploy
  - verify

# =============================================================================
# STAGE 1: Install
# =============================================================================

install-backend:
  stage: install
  image: node:18
  script:
    - npm ci
  cache:
    key: backend-${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
    policy: push

install-frontend:
  stage: install
  image: node:18
  script:
    - cd frontend && npm ci
  cache:
    key: frontend-${CI_COMMIT_REF_SLUG}
    paths:
      - frontend/node_modules/
    policy: push

# =============================================================================
# STAGE 2: Lint
# =============================================================================

lint-backend:
  stage: lint
  image: node:18
  cache:
    key: backend-${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
    policy: pull
  script:
    - test -d node_modules || npm ci
    - node -c backend/server.js
    - node -c backend/routes/*.js
    - node -c backend/helpers/*.js
    - node -c backend/middleware/*.js
  needs:
    - install-backend

lint-frontend:
  stage: lint
  image: node:18
  cache:
    key: frontend-${CI_COMMIT_REF_SLUG}
    paths:
      - frontend/node_modules/
    policy: pull
  script:
    - cd frontend && (test -d node_modules || npm ci) && npx eslint src/ --ignore-pattern '**/__tests__/**' --ignore-pattern '**/*.test.js' --max-warnings 25
  needs:
    - install-frontend

# =============================================================================
# STAGE 3: Test
# =============================================================================

test-backend:
  stage: test
  image: node:18
  variables:
    DATABASE_URL: $DATABASE_URL
  cache:
    key: backend-${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
    policy: pull
  script:
    - test -d node_modules || npm ci
    - ./node_modules/.bin/jest --ci --forceExit
  timeout: 5 minutes
  needs:
    - install-backend

test-frontend:
  stage: test
  image: node:18
  cache:
    - key: frontend-${CI_COMMIT_REF_SLUG}
      paths:
        - frontend/node_modules/
      policy: pull
    - key: backend-${CI_COMMIT_REF_SLUG}
      paths:
        - node_modules/
      policy: pull
  script:
    - cd frontend && npm ci && cd .. && (test -d node_modules || npm ci) && cd frontend && CI=true npx react-scripts test --watchAll=false --ci
  timeout: 5 minutes
  needs:
    - install-frontend

# =============================================================================
# STAGE 4: Build
# =============================================================================

build-frontend:
  stage: build
  image: node:18
  cache:
    key: frontend-${CI_COMMIT_REF_SLUG}
    paths:
      - frontend/node_modules/
    policy: pull
  script:
    - cd frontend && (test -d node_modules || npm ci) && CI=false REACT_APP_API_BASE=/api REACT_APP_API_HOST="" npm run build
  artifacts:
    paths:
      - frontend/build/
    expire_in: 7 days
  needs:
    - test-frontend
    - lint-frontend

# =============================================================================
# STAGE 5: Deploy (SSH from container)
# =============================================================================

.deploy-base: &deploy-base
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client rsync
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_ed25519
    - chmod 600 ~/.ssh/id_ed25519
    - echo -e "Host *\n  StrictHostKeyChecking no\n  UserKnownHostsFile /dev/null" > ~/.ssh/config

deploy-staging:
  <<: *deploy-base
  stage: deploy
  rules:
    - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
      when: on_success
  environment:
    name: staging
    url: http://71.85.90.9:3100
  script:
    - echo "Deploying to staging (${STAGING_HOST})..."
    - rsync -az --delete
        --exclude='.git'
        --exclude='node_modules'
        --exclude='frontend/node_modules'
        --exclude='frontend/build'
        --exclude='backend/uploads'
        --exclude='*.log'
        --exclude='*.db'
        --exclude='.env'
        ./ ${STAGING_USER}@${STAGING_HOST}:${STAGING_DIR}/
    - rsync -az frontend/build/ ${STAGING_USER}@${STAGING_HOST}:${STAGING_DIR}/frontend/build/
    - ssh ${STAGING_USER}@${STAGING_HOST} "cd ${STAGING_DIR} && npm ci --prefer-offline"
    - ssh ${STAGING_USER}@${STAGING_HOST} "cd ${STAGING_DIR}/frontend && npm ci --prefer-offline"
    - ssh ${STAGING_USER}@${STAGING_HOST} "cd ${STAGING_DIR}/backend && node migrations/run-all.js"
    - ssh ${STAGING_USER}@${STAGING_HOST} "cd ${STAGING_DIR}/backend && test -f certs/cert.pem || (mkdir -p certs && openssl req -x509 -newkey rsa:2048 -keyout certs/key.pem -out certs/cert.pem -days 365 -nodes -subj '/CN=cve-dashboard.local' 2>/dev/null)"
    - ssh ${STAGING_USER}@${STAGING_HOST} "systemctl restart cve-backend-staging || systemctl start cve-backend-staging || true"
    - echo "Staging deploy complete."
  after_script:
    - |
      apk add --no-cache curl > /dev/null 2>&1
      ISSUES=$(git log --format=%B -1 | grep -oP '#\d+' | tr -d '#' | sort -u)
      for ISSUE in $ISSUES; do
        curl --silent --request POST \
          --header "PRIVATE-TOKEN: ${GITLAB_PAT}" \
          "${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/issues/${ISSUE}/notes" \
          --data-urlencode "body=✅ Deployed to **staging** in pipeline [#${CI_PIPELINE_ID}](${CI_PIPELINE_URL}) (commit \`${CI_COMMIT_SHORT_SHA}\`)" \
          > /dev/null 2>&1 || true
      done
  needs:
    - build-frontend
    - test-backend

deploy-production:
  <<: *deploy-base
  stage: deploy
  rules:
    - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
      when: manual
  environment:
    name: production
    url: http://71.85.90.6:3001
  script:
    - echo "Deploying to production (${PROD_HOST})..."
    - ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && git rev-parse HEAD 2>/dev/null || echo none" > /tmp/prod-prev-commit
    - echo "Previous production commit:$(cat /tmp/prod-prev-commit)"
    - rsync -az --delete
        --exclude='.git'
        --exclude='node_modules'
        --exclude='frontend/node_modules'
        --exclude='frontend/build'
        --exclude='backend/uploads'
        --exclude='*.log'
        --exclude='*.db'
        --exclude='.env'
        --exclude='.compliance-staging'
        ./ ${PROD_USER}@${PROD_HOST}:${PROD_DIR}/
    - rsync -az frontend/build/ ${PROD_USER}@${PROD_HOST}:${PROD_DIR}/frontend/build/
    - ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && npm ci --prefer-offline"
    - ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR}/frontend && npm ci --prefer-offline"
    - ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR}/backend && node migrations/run-all.js"
    - ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR}/backend && test -f certs/cert.pem || (mkdir -p certs && openssl req -x509 -newkey rsa:2048 -keyout certs/key.pem -out certs/cert.pem -days 365 -nodes -subj '/CN=cve-dashboard.local' 2>/dev/null)"
    - ssh ${PROD_USER}@${PROD_HOST} "test -f /etc/systemd/system/cve-backend.service || true"
    - ssh ${PROD_USER}@${PROD_HOST} "systemctl daemon-reload && systemctl enable cve-backend && systemctl restart cve-backend"
    - echo "Production deploy complete."
  after_script:
    - |
      apk add --no-cache curl > /dev/null 2>&1
      ISSUES=$(git log --format=%B -1 | grep -oP '#\d+' | tr -d '#' | sort -u)
      for ISSUE in $ISSUES; do
        curl --silent --request POST \
          --header "PRIVATE-TOKEN: ${GITLAB_PAT}" \
          "${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/issues/${ISSUE}/notes" \
          --data-urlencode "body=🚀 Deployed to **production** in pipeline [#${CI_PIPELINE_ID}](${CI_PIPELINE_URL}) (commit \`${CI_COMMIT_SHORT_SHA}\`)" \
          > /dev/null 2>&1 || true
      done
  needs:
    - build-frontend
    - test-backend

# =============================================================================
# STAGE 6: Verify
# =============================================================================

verify-staging:
  stage: verify
  image: alpine:latest
  rules:
    - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
      when: on_success
  script:
    - apk add --no-cache curl
    - echo "Verifying staging..."
    - sleep 3
    - |
      for i in 1 2 3 4 5; do
        STATUS=$(curl -sk -o /dev/null -w "%{http_code}" https://${STAGING_HOST}:3100/api/health 2>/dev/null || echo "000")
        if [ "$STATUS" = "200" ]; then
          echo "Staging health check passed (attempt $i)"
          break
        fi
        # Fall back to HTTP in case TLS is not enabled
        STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://${STAGING_HOST}:3100/api/health 2>/dev/null || echo "000")
        if [ "$STATUS" = "200" ]; then
          echo "Staging health check passed via HTTP (attempt $i)"
          break
        fi
        echo "Staging not ready (status: $STATUS), retrying... (attempt $i/5)"
        sleep 3
      done
      if [ "$STATUS" != "200" ]; then
        echo "FAILED: Staging health check failed after 5 attempts"
        exit 1
      fi
    - |
      COMP_STATUS=$(curl -sk -o /dev/null -w "%{http_code}" "https://${STAGING_HOST}:3100/api/compliance/items?page=1&limit=1" || echo "000")
      [ "$COMP_STATUS" != "200" ] && echo "WARN: Compliance items returned $COMP_STATUS" || true
    - |
      VCL_STATUS=$(curl -sk -o /dev/null -w "%{http_code}" "https://${STAGING_HOST}:3100/api/compliance/vcl/stats" || echo "000")
      [ "$VCL_STATUS" != "200" ] && echo "WARN: VCL stats returned $VCL_STATUS" || true
    - echo "Staging verification passed."
  needs:
    - deploy-staging

verify-production:
  stage: verify
  image: alpine:latest
  rules:
    - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
      when: on_success
  script:
    - apk add --no-cache curl openssh-client
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_ed25519
    - chmod 600 ~/.ssh/id_ed25519
    - echo -e "Host *\n  StrictHostKeyChecking no\n  UserKnownHostsFile /dev/null" > ~/.ssh/config
    - echo "Verifying production..."
    - sleep 3
    - |
      for i in 1 2 3 4 5 6 7 8 9 10; do
        STATUS=$(curl -sk -o /dev/null -w "%{http_code}" https://${PROD_HOST}:3001/api/health 2>/dev/null || echo "000")
        if [ "$STATUS" = "200" ]; then
          echo "Production health check passed (attempt $i)"
          break
        fi
        # Fall back to HTTP in case TLS is not enabled
        STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://${PROD_HOST}:3001/api/health 2>/dev/null || echo "000")
        if [ "$STATUS" = "200" ]; then
          echo "Production health check passed via HTTP (attempt $i)"
          break
        fi
        echo "Production not ready (status: $STATUS), retrying... (attempt $i/10)"
        sleep 3
      done
      if [ "$STATUS" != "200" ]; then
        echo "FAILED: Production health check failed — initiating rollback"
        PREV_COMMIT=$(cat /tmp/prod-prev-commit 2>/dev/null || echo "")
        if [ -n "$PREV_COMMIT" ] && [ "$PREV_COMMIT" != "none" ]; then
          echo "Rolling back to $PREV_COMMIT..."
          ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && git checkout ${PREV_COMMIT} --force 2>/dev/null" || true
          ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && npm ci --prefer-offline"
          ssh ${PROD_USER}@${PROD_HOST} "systemctl restart cve-backend"
          echo "Rollback complete. Verify manually."
        else
          echo "No previous commit recorded — manual intervention required."
        fi
        exit 1
      fi
    - |
      COMP_STATUS=$(curl -sk -o /dev/null -w "%{http_code}" "https://${PROD_HOST}:3001/api/compliance/items?page=1&limit=1" || echo "000")
      [ "$COMP_STATUS" != "200" ] && echo "WARN: Compliance items returned $COMP_STATUS" || true
    - |
      VCL_STATUS=$(curl -sk -o /dev/null -w "%{http_code}" "https://${PROD_HOST}:3001/api/compliance/vcl/stats" || echo "000")
      [ "$VCL_STATUS" != "200" ] && echo "WARN: VCL stats returned $VCL_STATUS" || true
    - echo "Production verification passed."
  needs:
    - deploy-production
  allow_failure: false
