pipeline {
    // Koi default agent nahi. Har stage khud batati hai kahan chalegi -
    // CI builder VM par, CD controller par. Isse build ko production ki
    // chaabi kabhi nahi milti.
    agent none

    parameters {
        string(name: 'ROLLBACK_TAG', defaultValue: '',
               description: 'Khaali = normal build. Purana build number (jaise 2) = seedha us image par rollback, bina rebuild ke')
    }

    environment {
        REGISTRY   = 'ns31240276.ip-51-195-4.eu:5000'
        IMAGE_NAME = 'swim/demo-app'
        // Sab jagah se ek hi naam. Controller isse proxy par bhejta hai
        // (extra_hosts), builder VM bridge IP par (/etc/hosts). TLS cert
        // bhi isi naam ka hai, isliye andar ke DNS naam use nahi karte.
        VAULT_ADDR = 'https://ns31240276.ip-51-195-4.eu:8444'
        SONAR_URL  = 'https://ns31240276.ip-51-195-4.eu:8446'
        HOST_BRIDGE_IP = '192.168.124.1'
        TRIVY_SEVERITY = 'HIGH,CRITICAL'
    }

    // Trigger ko YAHAN declare karna zaroori hai. Sirf config.xml me hone par
    // DeclarativeJobPropertyTrackerAction NPE deta hai.
    triggers {
        pollSCM('')
    }

    options {
        timestamps()
        buildDiscarder(logRotator(numToKeepStr: '20'))
        timeout(time: 40, unit: 'MINUTES')
        disableConcurrentBuilds()
    }

    stages {

        // ============ CI: sab kuch builder VM par ============
        // Yahan docker daemon builder VM ka hai. Build escape hui to attacker ko
        // sirf ye VM milti hai - na secrets, na Vault, na production ki key.
        stage('CI (builder-vm-01)') {
            agent { label 'builder' }

            stages {
                stage('Init') {
                    steps {
                        script {
                            // params ko environment block me mat chhuo - wahan null hone par NPE.
                            def rb = (params.ROLLBACK_TAG ?: '').trim()
                            env.IS_ROLLBACK = rb ? 'true' : 'false'
                            env.IMAGE_TAG   = rb ? rb : "${env.BUILD_NUMBER}"
                            echo "NODE      : ${env.NODE_NAME}"
                            echo "MODE      : ${env.IS_ROLLBACK == 'true' ? 'ROLLBACK' : 'NORMAL BUILD'}"
                            echo "IMAGE_TAG : ${env.IMAGE_TAG}"
                        }
                    }
                }

                stage('Checkout') {
                    steps {
                        checkout scm
                        sh 'echo "commit: $(git rev-parse --short HEAD)"'
                    }
                }

                stage('Secrets from Vault') {
                    steps {
                        // Token ab file se nahi, Jenkins credential se aata hai aur
                        // log me masked rehta hai. Ye token sirf swim/registry padh
                        // sakta hai - baaki Vault iske liye band hai.
                        withCredentials([string(credentialsId: 'vault-builder-token', variable: 'VAULT_TOKEN')]) {
                            sh '''
                                set +x
                                curl -sf -H "X-Vault-Token: $VAULT_TOKEN" \
                                     "${VAULT_ADDR}/v1/swim/data/registry" \
                                  | python3 -c "
import sys, json
d = json.load(sys.stdin)['data']['data']
open('.vault-registry.env','w').write(
    'REG_USER=%s\\nREG_PASS=%s\\n' % (d['username'], d['password']))
print('  Vault se registry creds mil gaye (user=%s)' % d['username'])
"
                                chmod 600 .vault-registry.env
                            '''
                        }
                    }
                }

                stage('Test') {
                    when { expression { env.IS_ROLLBACK != 'true' } }
                    steps {
                        sh '''
                            docker run --rm -v "$PWD":/src -w /src python:3.12-slim sh -c "
                                pip install --quiet -r requirements-dev.txt &&
                                python -m pytest tests/ -v
                            "
                        '''
                    }
                }

                stage('Code quality (SonarQube)') {
                    when { expression { env.IS_ROLLBACK != 'true' } }
                    steps {
                        withCredentials([string(credentialsId: 'sonar-token', variable: 'SONAR_TOKEN')]) {
                            sh '''
                                set +x
                                docker run --rm \
                                  --add-host "ns31240276.ip-51-195-4.eu:${HOST_BRIDGE_IP}" \
                                  -e SONAR_HOST_URL="${SONAR_URL}" \
                                  -e SONAR_TOKEN="$SONAR_TOKEN" \
                                  -v "$PWD":/usr/src \
                                  sonarsource/sonar-scanner-cli:11 \
                                    -Dsonar.projectKey=swim-demo-app
                            '''
                        }
                    }
                }

                stage('Build image') {
                    when { expression { env.IS_ROLLBACK != 'true' } }
                    steps {
                        sh '''
                            docker build \
                              --build-arg APP_VERSION=${IMAGE_TAG} \
                              -t ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \
                              -t ${REGISTRY}/${IMAGE_NAME}:latest .
                        '''
                    }
                }

                stage('Security scan (Trivy)') {
                    when { expression { env.IS_ROLLBACK != 'true' } }
                    steps {
                        sh '''
                            # GATE hai, report nahi: HIGH/CRITICAL mila to build yahin fail hogi.
                            # Jo CVE business ne accept kiye hain wo .trivyignore me hain.
                            docker run --rm \
                              -v /var/run/docker.sock:/var/run/docker.sock \
                              -v trivy-cache:/root/.cache/ \
                              -v "$PWD/.trivyignore":/.trivyignore:ro \
                              aquasec/trivy:0.58.1 image \
                                --severity ${TRIVY_SEVERITY} \
                                --ignore-unfixed \
                                --ignorefile /.trivyignore \
                                --exit-code 1 \
                                --format table \
                                ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
                        '''
                    }
                }

                stage('Smoke test image') {
                    when { expression { env.IS_ROLLBACK != 'true' } }
                    steps {
                        sh '''
                            CID=$(docker run -d -P ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG})
                            trap "docker rm -f $CID >/dev/null 2>&1" EXIT
                            sleep 5
                            docker exec $CID python -c "
import urllib.request, json
r = json.loads(urllib.request.urlopen('http://127.0.0.1:8000/version').read())
print('version endpoint:', r)
assert r['version'] == '${IMAGE_TAG}', 'version galat!'
"
                        '''
                    }
                }

                stage('Push to registry') {
                    when { expression { env.IS_ROLLBACK != 'true' } }
                    steps {
                        sh '''
                            set +x
                            . ./.vault-registry.env
                            echo "$REG_PASS" | docker login ${REGISTRY} -u "$REG_USER" --password-stdin
                            docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
                            docker push ${REGISTRY}/${IMAGE_NAME}:latest
                            docker logout ${REGISTRY}
                        '''
                    }
                }
            }

            post {
                always {
                    sh 'rm -f .vault-registry.env || true'
                    sh 'docker image prune -f --filter "until=24h" || true'
                    cleanWs()
                }
            }
        }

        // ============ CD: controller par ============
        // Deploy key sirf yahan aati hai. Builder VM ise kabhi nahi dekhti.
        stage('Deploy STAGING') {
            agent { label 'built-in' }
            steps {
                checkout scm
                withCredentials([
                    sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'DEPLOY_KEY', usernameVariable: 'DEPLOY_USER'),
                    usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')
                ]) {
                    sh '''
                        set +x
                        export ANSIBLE_HOST_KEY_CHECKING=False
                        ansible-galaxy collection install community.docker --force -q || true
                        ansible-playbook -i deploy/inventory.ini deploy/deploy.yml \
                          -u "$DEPLOY_USER" --private-key "$DEPLOY_KEY" \
                          -e target=staging
                    '''
                }
                sh '''
                    SVM=$(grep "^staging-vm" deploy/inventory.ini | sed "s/.*ansible_host=//" | tr -d " ")
                    GOT=$(curl -sf --max-time 10 http://${SVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])")
                    echo "  staging expected: ${IMAGE_TAG}  |  live: ${GOT}"
                    [ "$GOT" = "${IMAGE_TAG}" ] || { echo "STAGING MISMATCH"; exit 1; }
                    echo "  STAGING VERIFIED"
                '''
            }
        }

        stage('Production approval') {
            // agent none = intezaar ke 15 minute me koi executor block nahi hota
            agent none
            steps {
                script {
                    timeout(time: 15, unit: 'MINUTES') {
                        input(message: "Staging par ${env.IMAGE_TAG} verified hai. PRODUCTION par deploy karein?",
                              ok: 'Deploy to Production')
                    }
                    echo "Production deploy approve ho gaya"
                }
            }
        }

        stage('Deploy PRODUCTION') {
            agent { label 'built-in' }
            steps {
                checkout scm
                withCredentials([
                    sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'DEPLOY_KEY', usernameVariable: 'DEPLOY_USER'),
                    usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')
                ]) {
                    sh '''
                        set +x
                        export ANSIBLE_HOST_KEY_CHECKING=False
                        ansible-playbook -i deploy/inventory.ini deploy/deploy.yml \
                          -u "$DEPLOY_USER" --private-key "$DEPLOY_KEY" \
                          -e target=production
                    '''
                }
                sh '''
                    PVM=$(grep "^app-vm" deploy/inventory.ini | sed "s/.*ansible_host=//" | tr -d " ")
                    GOT=$(curl -sf --max-time 10 http://${PVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])")
                    echo "  production expected: ${IMAGE_TAG}  |  live: ${GOT}"
                    [ "$GOT" = "${IMAGE_TAG}" ] || { echo "PROD MISMATCH"; exit 1; }
                    echo "  PRODUCTION VERIFIED"
                '''
            }
            post { always { cleanWs() } }
        }
    }

    post {
        success { echo "BUILD ${env.BUILD_NUMBER} SAFAL -> ${env.REGISTRY}/${env.IMAGE_NAME}:${env.IMAGE_TAG}" }
        failure { echo "BUILD ${env.BUILD_NUMBER} FAIL" }
    }
}
