/*
 * blut24-landing — CI/CD
 *
 * Trigger : push to main on git@github.com:younex1/blut24-landing.git
 * Jenkins : http://192.168.12.65:8080/
 * Target  : 192.168.12.81, live at /var/www/blut24.com/web/current
 *
 * Jenkins only ORCHESTRATES. The install/build happens on the target host,
 * because better-sqlite3 is a native addon that must be compiled against the
 * Node ABI, OS and libc it will actually run on. Availability is unaffected —
 * the previous release keeps serving until deploy.sh has built, health-checked
 * and atomically swapped the new one.
 *
 * Setup for this job is documented in deploy/README.md.
 */
pipeline {
  agent any

  options {
    // NOTE: disableConcurrentBuilds() was removed deliberately. With it set, every build
    // after the first sat in the queue as a BlockedItem and never started until Jenkins
    // was restarted — observed three times on this controller. The race it guards against
    // is two deploys swapping the same symlink, which is a resource on the TARGET, not on
    // Jenkins; deploy.sh now takes an flock there instead, which also protects against
    // deploys started by hand or from another controller.
    buildDiscarder(logRotator(numToKeepStr: '30'))
    timeout(time: 25, unit: 'MINUTES')
    timestamps()
  }

  triggers {
    githubPush()                          // fires on the GitHub webhook
    pollSCM('H/5 * * * *')                // safety net if the webhook cannot reach Jenkins
  }

  environment {
    DEPLOY_HOST  = '192.168.12.81'
    DEPLOY_USER  = 'root'
    APP_ROOT     = '/var/www/blut24.com/web'
    APP_NAME     = 'blut24-landing'
    // No credential binding: the `ssh-agent` plugin is not installed on this
    // controller and it is shared with 19 other jobs, so it cannot be restarted
    // to add one. The jenkins user's own key (~/.ssh/id_rsa) is authorised for
    // root@192.168.12.81 instead, which needs no plugin at all.
    SSH_OPTS     = '-o StrictHostKeyChecking=accept-new -o BatchMode=yes -o ConnectTimeout=15'
  }

  stages {
    stage('Checkout') {
      steps {
        checkout scm
        script {
          env.GIT_SHA     = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
          env.RELEASE_ID  = "${env.BUILD_NUMBER}-${env.GIT_SHA}"
          env.RELEASE_DIR = "${env.APP_ROOT}/releases/${env.RELEASE_ID}"
          currentBuild.displayName = "#${env.BUILD_NUMBER} ${env.GIT_SHA}"
        }
        sh 'echo "Deploying $RELEASE_ID from $(git log -1 --pretty=%s)"'
      }
    }

    /*
     * Guard rail: main only. The webhook is branch-agnostic, so a push to any
     * other branch would otherwise deploy to production.
     */
    stage('Guard: main only') {
      when {
        not { anyOf { branch 'main'; expression { env.GIT_BRANCH?.endsWith('/main') } } }
      }
      steps {
        script {
          currentBuild.result = 'NOT_BUILT'
          error("Branch '${env.GIT_BRANCH}' is not main — nothing deployed.")
        }
      }
    }

    stage('Ship source to target') {
      steps {
          sh '''
            set -eu
            SSH="ssh ${SSH_OPTS}"

            $SSH ${DEPLOY_USER}@${DEPLOY_HOST} "mkdir -p ${RELEASE_DIR}"

            # Ship the working tree, not a git clone — the target needs no git and
            # no GitHub credentials. Excludes are the things the target builds or
            # owns itself; data/ in particular is a symlink to shared/ on the host
            # and must never be overwritten by the repo copy.
            rsync -az --delete \
              -e "$SSH" \
              --exclude '.git' \
              --exclude 'node_modules' \
              --exclude '.nuxt' \
              --exclude '.output' \
              --exclude 'data/*.db' \
              --exclude 'data/*.db-*' \
              --exclude '.env' \
              ./ ${DEPLOY_USER}@${DEPLOY_HOST}:${RELEASE_DIR}/
          '''
      }
    }

    stage('Build, health-check and swap') {
      steps {
          sh '''
            set -eu
            ssh ${SSH_OPTS} \
              ${DEPLOY_USER}@${DEPLOY_HOST} \
              "RELEASE_ID=${RELEASE_ID} bash ${RELEASE_DIR}/deploy/deploy.sh"
          '''
      }
    }

    stage('Smoke test') {
      steps {
          sh '''
            set -eu
            # Independent of deploy.sh's own check: assert the LIVE symlink really
            # points at this build and that the site answers through it.
            ssh ${SSH_OPTS} \
              ${DEPLOY_USER}@${DEPLOY_HOST} "
                set -eu
                live=\\$(basename \\$(readlink -f ${APP_ROOT}/current))
                [ \\"\\$live\\" = \\"${RELEASE_ID}\\" ] || { echo \\"web -> \\$live, expected ${RELEASE_ID}\\"; exit 1; }
                port=\\$(grep -E '^NITRO_PORT=' /var/www/blut24.com/private/blut24-landing/shared/.env | cut -d= -f2 || echo 3002)
                code=\\$(curl -s -o /dev/null -w '%{http_code}' -m 10 http://127.0.0.1:\\$port/)
                [ \\"\\$code\\" = \\"200\\" ] || { echo \\"health check returned \\$code\\"; exit 1; }
                echo \\"smoke OK: \\$live on :\\$port\\"
              "
          '''
      }
    }
  }

  post {
    success { echo "Deployed ${env.RELEASE_ID} to ${env.DEPLOY_HOST}:${env.APP_ROOT}/current" }
    failure {
      echo "Deploy of ${env.RELEASE_ID} FAILED. deploy.sh rolls the symlink back automatically; " +
           "the previous release should still be live. Verify with: " +
           "ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} 'readlink -f ${env.APP_ROOT}/current && pm2 list'"
    }
  }
}
