pipeline {
    agent none

    stages {
        stage('Checkout') {
            agent any
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    checkout scm
                    script {
                        // Se calcula solo, a partir del repo de cada grupo. Nadie edita esto.
                        env.SONAR_PROJECT_KEY = env.GIT_URL.tokenize('/')[-1].replaceAll('\\.git$', '')

                        def safeId = "${env.BUILD_TAG}".replaceAll('[^a-zA-Z0-9_.-]', '-')
                        env.APP_NET       = "net-${safeId}"
                        // Alias corto y fijo: un nombre de host DNS no puede pasar de 63 caracteres y
                        // con el BUILD_TAG de una rama "integracion/..." quedaba de ~75. Es único dentro
                        // de la red, porque cada build crea la suya (APP_NET).
                        env.BACKEND_ALIAS = "backend"
                    }
                }
            }
        }

        stage('Backend Pipeline') {
            when {
                expression { fileExists('backend/pom.xml') }
            }
            agent {
                docker {
                    image 'maven:3.9-eclipse-temurin-21'
                    args '-v /home/proyecto/.m2:/home/proyecto/.m2'
                }
            }
            stages {
                stage('Backend: build y pruebas') {
                    options {
                        timeout(time: 5, unit: 'MINUTES')
                    }
                    steps {
                        dir('backend') {
                            // JaCoCo se invoca por línea de comandos (sin tocar el pom.xml): instrumenta
                            // las pruebas y deja target/site/jacoco/jacoco.xml para SonarQube.
                            sh '''
                                mvn -B clean \
                                  org.jacoco:jacoco-maven-plugin:0.8.13:prepare-agent \
                                  test \
                                  org.jacoco:jacoco-maven-plugin:0.8.13:report \
                                  -Dmaven.repo.local=/home/proyecto/.m2/repository
                            '''
                        }
                    }
                }

                stage('Resultados de pruebas') {
                    options {
                        timeout(time: 5, unit: 'MINUTES')
                    }
                    steps {
                        dir('backend') {
                            junit 'target/surefire-reports/*.xml'
                        }
                    }
                }

                stage('Backend: empaquetar') {
                    options {
                        timeout(time: 5, unit: 'MINUTES')
                    }
                    steps {
                        dir('backend') {
                            sh 'mvn -B package -DskipTests -Dmaven.repo.local=/home/proyecto/.m2/repository'
                            archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
                            stash name: 'backend-jar', includes: 'target/*.jar'
                            // Sonar corre en otro agente: necesita las clases compiladas y el
                            // reporte de cobertura que JaCoCo dejó durante `mvn test`.
                            stash name: 'backend-analysis', includes: 'target/classes/**,target/site/jacoco/jacoco.xml'
                        }
                    }
                }
            }
        }

        stage('Frontend: build y pruebas') {
            when {
                expression { fileExists('frontend/package.json') }
            }
            agent {
                docker { image 'node:22-slim' }
            }
            environment {
                COREPACK_HOME = "${WORKSPACE}/.corepack"
                XDG_CACHE_HOME = "${WORKSPACE}/.cache"
            }
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    dir('frontend') {
                        sh '''
                            corepack pnpm install --frozen-lockfile
                            # Solo para el reporte de cobertura, y solo si el proyecto no lo trae ya:
                            # se instala en este workspace temporal (no toca package.json ni el lockfile)
                            # con la misma versión de vitest que use cada proyecto.
                            if [ ! -d node_modules/@vitest/coverage-v8 ]; then
                              VITEST_VERSION=$(node -p "require('./node_modules/vitest/package.json').version")
                              corepack pnpm add -D @vitest/coverage-v8@$VITEST_VERSION
                            fi
                            corepack pnpm run build
                            corepack pnpm test --coverage --coverage-reporters=lcov
                        '''
                        stash name: 'frontend-coverage', includes: 'coverage/**/lcov.info'
                    }
                }
            }
            post {
                always {
                    sh 'rm -rf .cache .corepack'
                }
            }
        }

        stage('Analisis SonarQube (Backend + Frontend)') {
            when {
                expression { fileExists('backend/pom.xml') && fileExists('frontend/package.json') }
            }
            agent {
                docker {
                    image 'sonarsource/sonar-scanner-cli:latest'
                    args "--entrypoint=''"
                }
            }
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    dir('backend') {
                        unstash 'backend-analysis'
                    }
                    dir('frontend') {
                        unstash 'frontend-coverage'
                    }
                    withSonarQubeEnv('sonarqube-isis2211') {
                        sh '''
                            sonar-scanner \
                              -Dsonar.userHome=$WORKSPACE/.sonar \
                              -Dsonar.projectKey=$SONAR_PROJECT_KEY \
                              -Dsonar.sources=backend/src/main/java,frontend/src \
                              -Dsonar.tests=backend/src/test/java,frontend/src \
                              -Dsonar.test.inclusions=**/*.spec.ts,**/*Test.java,**/*Tests.java \
                              -Dsonar.java.binaries=backend/target/classes \
                              -Dsonar.coverage.jacoco.xmlReportPaths=backend/target/site/jacoco/jacoco.xml \
                              -Dsonar.javascript.lcov.reportPaths=frontend/coverage/**/lcov.info \
                              -Dsonar.coverage.exclusions=**/main.ts,**/environments/**,**/app.config.ts,**/*Application.java \
                              -Dsonar.exclusions=**/node_modules/**,**/target/**,**/.sonar/**
                        '''
                    }
                }
            }
        }

        stage('Quality Gate') {
            when {
                expression { fileExists('backend/pom.xml') && fileExists('frontend/package.json') }
            }
            agent any
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    waitForQualityGate abortPipeline: false
                }
            }
        }

        stage('API: Postman/Newman') {
            when {
                expression { fileExists('backend/pom.xml') }
            }
            agent any
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    dir('backend') {
                        unstash 'backend-jar'
                    }
                    script {
                        sh 'docker network inspect $APP_NET >/dev/null 2>&1 || docker network create $APP_NET'

                        docker.image('eclipse-temurin:21-jre-alpine').withRun(
                            "--network ${env.APP_NET} --network-alias ${env.BACKEND_ALIAS} -v ${WORKSPACE}/backend/target:/app -w /app",
                            "sh -c 'java -jar \$(ls *.jar | head -n1)'"
                        ) { backend ->
                            sh "sleep 3 && docker logs ${backend.id}"

                            docker.image('postman/newman:alpine').inside("--entrypoint='' --network ${env.APP_NET}") {
                                dir('backend/collections') {
                                    sh '''
                                        intentos=0
                                        until nc -z $BACKEND_ALIAS 8080; do
                                          intentos=$((intentos + 1))
                                          if [ $intentos -ge 90 ]; then
                                            echo "El backend no respondio en 90 s: revisa el arranque de Spring (docker logs)."
                                            exit 1
                                          fi
                                          echo "Esperando a que el backend levante..."
                                          sleep 1
                                        done

                                        for coleccion in *.postman_collection.json; do
                                          echo "== $coleccion =="
                                          NODE_OPTIONS="--max-old-space-size=512" newman run "$coleccion" --env-var baseUrl="http://$BACKEND_ALIAS:8080" --reporter-cli-no-summary-timers
                                        done
                                    '''
                                }
                            }
                        }
                    }
                }
            }
            post {
                always {
                    sh 'docker network rm $APP_NET || true'
                }
            }
        }
    }

    post {
        failure {
            echo 'Build o pruebas fallaron.'
        }
    }
}
