/*
 * Jenkins pipeline for natinfo_flutter
 * - dev branch: build unsigned APKs (no NDK install, no upload)
 * - v*  tag  : install NDK, build signed APKs, rename, upload
 */

pipeline {
    agent { label 'android-x86' }

    environment {
        FLUTTER_ROOT = "${HOME}/flutter"
        PATH = "${env.PATH}:${FLUTTER_ROOT}/bin:${HOME}/.pub-cache/bin:" +
               "${ANDROID_HOME}/platform-tools:" +
               "${ANDROID_HOME}/cmdline-tools/latest/bin"
        ANDROID_HOME     = "/home/jenkins/android-sdk"
        ANDROID_SDK_ROOT = "/home/jenkins/android-sdk"
        // défini plus tard par le stage disorderfs :
        // SRC_DIR = "<path monté via disorderfs>" ou "." par défaut
    }

    stages {

        /*────────────────────────────────────────────────────────────*/
        stage('Install Flutter & Activate FVM') {
            steps {
                sh '''
                  set -eux
                  if [ ! -d "$FLUTTER_ROOT" ]; then
                    echo "Cloning Flutter stable into $FLUTTER_ROOT"
                    git clone https://github.com/flutter/flutter.git -b stable --depth 1 "$FLUTTER_ROOT"
                  else
                    echo "Flutter already exists at $FLUTTER_ROOT"
                  fi
                  export PATH="$FLUTTER_ROOT/bin:$PATH"
                  flutter doctor -v
                  flutter pub global activate fvm
                '''
            }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Clean workspace & caches') {
            steps { deleteDir() }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Checkout code') {
            steps { checkout scm }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Deterministic FS (disorderfs mount)') {
            steps {
                script {
                    env.SRC_DIR = '.'
                }
                sh '''
                  set -eux
                  if command -v disorderfs >/dev/null 2>&1 && [ -e /dev/fuse ]; then
                    # Prepare mirrors
                    rm -rf "$WORKSPACE/_src" "$WORKSPACE/_build"
                    mkdir -p "$WORKSPACE/_src" "$WORKSPACE/_build"
                    rsync -a --delete ./ "$WORKSPACE/_src/"

                    # Mount with deterministic ordering (F-Droid docs recommend disorderfs)
                    # --sort-dirents=yes ensures stable lexicographic order of directory entries.
                    disorderfs --sort-dirents=yes --reverse-dirents=no "$WORKSPACE/_src" "$WORKSPACE/_build" || {
                      echo "WARNING: disorderfs mount failed; proceeding without it."
                      rmdir "$WORKSPACE/_build" || true
                    }

                    if mountpoint -q "$WORKSPACE/_build"; then
                      echo "disorderfs mount active at _build"
                      echo "_build" > "$WORKSPACE/.disorderfs_mount_active"
                    fi
                  else
                    echo "disorderfs not available; proceeding without it."
                  fi
                '''
                script {
                    // if mount succeeded, use mounted dir
                    def marker = sh(script: 'test -f "$WORKSPACE/.disorderfs_mount_active" && echo 1 || echo 0', returnStdout: true).trim()
                    if (marker == '1') {
                        env.SRC_DIR = "${env.WORKSPACE}/_build"
                    } else {
                        env.SRC_DIR = '.'
                    }
                    echo "SRC_DIR set to: ${env.SRC_DIR}"
                }
            }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('FDroid-style prune (non-Android dirs)') {
            steps {
                dir(env.SRC_DIR ?: '.') {
                    sh '''
                      set -eux
                      rm -rf ios linux macos web windows
                    '''
                }
            }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Select Flutter version') {
            steps {
                dir(env.SRC_DIR ?: '.') {
                    script {
                        def pub = readYaml file: 'pubspec.yaml'
                        def flutterVersion = pub['flutter-version']
                        if (!flutterVersion) { error "No 'flutter-version' entry found in pubspec.yaml" }
                        env.FLUTTER_VERSION = flutterVersion.toString()
                    }
                    sh '''
                      set -eux
                      export PUB_CACHE="$WORKSPACE/.pub-cache-fdroid"
                      export PATH="$PUB_CACHE/bin:$PATH"
                      flutter pub global activate fvm
                      fvm install "${FLUTTER_VERSION}"
                      fvm use "${FLUTTER_VERSION}" --force
                      fvm flutter --version
                    '''
                }
            }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Ensure Android SDK & NDK') {
            steps {
                sh '''
                  set -eux
                  /opt/android/cmdline-tools/latest/bin/sdkmanager \
                    --sdk_root="$ANDROID_HOME" \
                    "platform-tools" \
                    "platforms;android-36" \
                    "build-tools;36.0.0" \
                    "ndk;27.0.12077973"
                  yes | /opt/android/cmdline-tools/latest/bin/sdkmanager \
                    --sdk_root="$ANDROID_HOME" --licenses
                '''
            }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Prepare signing material') {
            steps {
                dir(env.SRC_DIR ?: '.') {
                    withCredentials([
                        file  (credentialsId: 'natinfo-keystore', variable: 'KS'),
                        string(credentialsId: 'natinfo-ks-pass',  variable: 'KS_PASS'),
                        string(credentialsId: 'natinfo-key-pass', variable: 'KEY_PASS'),
                        string(credentialsId: 'natinfo-alias',    variable: 'ALIAS')
                    ]) {
                        sh '''
                          set -eux
                          mkdir -p android/app
                          cp -f "$KS" android/app/release.jks
                          cat > android/key.properties <<EOF
storeFile=release.jks
storePassword=$KS_PASS
keyAlias=$ALIAS
keyPassword=$KEY_PASS
EOF
                        '''
                    }
                }
            }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Flutter pub get') {
            steps {
                dir(env.SRC_DIR ?: '.') {
                    sh '''
                      set -eux
                      export PUB_CACHE="$WORKSPACE/.pub-cache-fdroid"
                      export PATH="$PUB_CACHE/bin:$PATH"
                      flutter pub global activate fvm
                      fvm flutter clean
                      rm -rf "$PUB_CACHE/hosted/pub.dev/geolocator_android-"*
                      fvm flutter pub get
                    '''
                }
            }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Patch geolocator_android') {
            steps {
                dir(env.SRC_DIR ?: '.') {
                    sh '''
                      set -eux
                      export PUB_CACHE="$WORKSPACE/.pub-cache-fdroid"
                      cd "${PUB_CACHE}/hosted/pub.dev"
                      plugin=$(ls -d geolocator_android-* | head -n 1)
                      if [ -z "$plugin" ]; then echo "ERROR: geolocator_android plugin not found"; exit 1; fi
                      cd "$plugin"
                      sed -i "/play-services-location/d" android/build.gradle
                      rm -f android/src/main/java/com/baseflow/geolocator/location/FusedLocationClient.java
                      GEO="android/src/main/java/com/baseflow/geolocator/location/GeolocationManager.java"
                      sed -i "/com\\.google\\.android\\.gms/d" "$GEO"
                      sed -i "/ConnectionResult/d" "$GEO"
                      sed -i "/GoogleApiAvailability/d" "$GEO"
                      sed -i "/public LocationClient createLocationClient/,/^  }/d" "$GEO"
                      sed -i "/private boolean isGooglePlayServicesAvailable/,/^  }/d" "$GEO"
                      sed -i "/implements .*ActivityResultListener {/a\\
  public LocationClient createLocationClient(\\
      Context context,\\
      boolean forceAndroidLocationManager,\\
      @Nullable LocationOptions locationOptions) {\\
    return new LocationManagerClient(context, locationOptions);\\
  }\\
" "$GEO"
                    '''
                }
            }
        }

        stage('Tune Gradle/Kotlin for CI') {
            steps {
                dir(env.SRC_DIR ?: '.') {
                sh '''
                    set -eux
                    GPROPS=android/gradle.properties
                    [ -f "$GPROPS" ] || touch "$GPROPS"

                    # Nettoie d'éventuelles définitions précédentes
                    sed -i '/^org\\.gradle\\.jvmargs=/d;
                            /^org\\.gradle\\.vfs\\.watch=/d;
                            /^org\\.gradle\\.workers\\.max=/d;
                            /^kotlin\\.daemon\\.jvm\\.options=/d;
                            /^org\\.gradle\\.daemon=/d' "$GPROPS"

                    cat >> "$GPROPS" <<'EOF'
            # Mémoire & GC côté Gradle
            org.gradle.jvmargs=-Xmx4096m -XX:+UseG1GC -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseContainerSupport
            # File-system watching off (FUSE/disorderfs)
            org.gradle.vfs.watch=false
            # Limiter le parallélisme pour lisser l'empreinte mémoire
            org.gradle.workers.max=2
            # Mémoire du daemon Kotlin (compilo)
            kotlin.daemon.jvm.options=-Xmx2048m
            EOF
                '''
                }
            }
            }

        /*────────────────────────────────────────────────────────────*/
        stage('Assemble split APKs (per-ABI, like F-Droid)') {
            steps {
                dir(env.SRC_DIR ?: '.') {
                    sh '''
                      set -eux
                      export PUB_CACHE="$WORKSPACE/.pub-cache-fdroid"
                      export PATH="$PUB_CACHE/bin:$PATH"
                      export PATH="$FLUTTER_ROOT/bin:$PATH"
                      fvm flutter build apk --release --split-per-abi --target-platform=android-x64   --verbose
                      fvm flutter build apk --release --split-per-abi --target-platform=android-arm   --verbose
                      fvm flutter build apk --release --split-per-abi --target-platform=android-arm64 --verbose
                    '''
                }
            }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Rename APKs') {
            steps {
                dir(env.SRC_DIR ?: '.') {
                    script {
                        def pub = readYaml file: 'pubspec.yaml'
                        if (!pub.version) error "No 'version:' in pubspec.yaml"
                        def version = pub.version.tokenize('+')[0]
                        sh """
                          set -eux
                          cd build/app/outputs/flutter-apk
                          for f in *-release.apk; do
                            abi=\$(echo \$f | cut -d'-' -f2)
                            mv "\$f" "net.retiolus.natinfo-\${abi}-v${version}.apk"
                          done
                        """
                    }
                }
            }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Archive APKs') {
        steps {
            // on se place dans le dossier monté _build si présent
            dir(env.SRC_DIR ?: '.') {
            archiveArtifacts artifacts: 'build/app/outputs/flutter-apk/net.retiolus.natinfo-*-v*.apk',
                                fingerprint: true
            }
        }
        }

        /*────────────────────────────────────────────────────────────*/
        stage('Publish release assets (Gitea, only on tag)') {
        when {
            anyOf {
                buildingTag()
                expression {
                    def branch = env.BRANCH_NAME ?: ''
                    return branch ==~ /^v\d+\.\d+\.\d+$/
                }
            }
        }
        steps {
            dir(env.SRC_DIR ?: '.') {
            publishGiteaAssets assets: 'build/app/outputs/flutter-apk/net.retiolus.natinfo-*-v*.apk'
            }
        }
        }
    }

    post {
        always {
            // démonte le mount disorderfs s'il est actif
            sh '''
              set +e
              if [ -f "$WORKSPACE/.disorderfs_mount_active" ]; then
                if mountpoint -q "$WORKSPACE/_build"; then
                  fusermount -u "$WORKSPACE/_build" 2>/dev/null || fusermount3 -u "$WORKSPACE/_build" 2>/dev/null || true
                fi
                rm -f "$WORKSPACE/.disorderfs_mount_active"
              fi
            '''
        }
    }
}
