name: Release
on:
  push:
    tags: ['v[0-9]+.[0-9]+.[0-9]+']

permissions:
  contents: write
  packages: write
  attestations: write
  id-token: write

env:
  PGRX_VERSION: "=0.17.0"

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Versions must agree (tag / Cargo.toml / build.gradle)
        run: |
          TAG_VERSION=${GITHUB_REF_NAME#v}
          CARGO_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)
          GRADLE_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' elastic/java/build.gradle | head -1)
          echo "tag=$TAG_VERSION cargo=$CARGO_VERSION gradle=$GRADLE_VERSION"
          if [ "$TAG_VERSION" != "$CARGO_VERSION" ] || [ "$TAG_VERSION" != "$GRADLE_VERSION" ]; then
            echo "::error::version mismatch: tag=$TAG_VERSION Cargo.toml=$CARGO_VERSION build.gradle=$GRADLE_VERSION"
            exit 1
          fi
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - run: cargo test -p kazsearch-core --test stem_tests
      - run: cargo build -p kazsearch-cli --release
      - run: ./target/release/kazsearch stem алмаларымыздағы

  build-deb:
    name: .deb / PG ${{ matrix.pg }}
    needs: test
    runs-on: ubuntu-latest
    strategy:
      matrix:
        pg: [16, 17, 18]
    steps:
      - uses: actions/checkout@v4

      - name: Install PostgreSQL ${{ matrix.pg }} dev headers
        run: |
          sudo apt-get update
          sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
          curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/pgdg.gpg
          sudo apt-get update
          sudo apt-get install -y postgresql-${{ matrix.pg }} postgresql-server-dev-${{ matrix.pg }} libclang-dev pkg-config build-essential

      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2

      - name: Install cargo-pgrx
        run: cargo install --locked cargo-pgrx --version "${{ env.PGRX_VERSION }}"

      - name: Init pgrx for PG ${{ matrix.pg }}
        run: cargo pgrx init --pg${{ matrix.pg }}=$(which pg_config)

      - name: Package extension
        run: |
          cargo pgrx package --pg-config $(which pg_config) \
            --features pg${{ matrix.pg }} --no-default-features -p pg_kazsearch

      - name: Build .deb
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          PKG_NAME=postgresql-${{ matrix.pg }}-pg-kazsearch
          PKG_DIR=${PKG_NAME}_${VERSION}_amd64
          PGRX_OUT=target/release/pg_kazsearch-pg${{ matrix.pg }}

          mkdir -p ${PKG_DIR}/DEBIAN

          cat > ${PKG_DIR}/DEBIAN/control << CTRL
          Package: ${PKG_NAME}
          Version: ${VERSION}
          Architecture: amd64
          Depends: postgresql-${{ matrix.pg }}
          Maintainer: Darkhan Akhmetov <darkhanahmetov2005@gmail.com>
          Description: Kazakh full-text search stemmer for PostgreSQL
           BFS suffix-stripping stemmer for Kazakh with vowel harmony,
           penalty scoring, lexicon verification, and stem repair.
          CTRL
          sed -i 's/^          //' ${PKG_DIR}/DEBIAN/control

          cp -a ${PGRX_OUT}/* ${PKG_DIR}/

          SHAREDIR=$(pg_config --sharedir)
          mkdir -p "${PKG_DIR}${SHAREDIR}/tsearch_data"
          cp data/tsearch_data/kaz_stems.dict "${PKG_DIR}${SHAREDIR}/tsearch_data/"
          cp data/tsearch_data/kaz_stems.dict.verbs "${PKG_DIR}${SHAREDIR}/tsearch_data/"
          cp data/tsearch_data/kaz_stopwords.stop "${PKG_DIR}${SHAREDIR}/tsearch_data/"

          dpkg-deb --build --root-owner-group ${PKG_DIR}
          echo "DEB_FILE=${PKG_DIR}.deb" >> $GITHUB_ENV

      - uses: actions/upload-artifact@v4
        with:
          name: deb-pg${{ matrix.pg }}
          path: ${{ env.DEB_FILE }}

  build-es-plugin:
    name: ES plugin / ${{ matrix.arch }}
    needs: test
    runs-on: ${{ matrix.runner }}
    strategy:
      matrix:
        include:
          - arch: x86_64
            runner: ubuntu-latest
            rust_target: x86_64-unknown-linux-gnu
          - arch: aarch64
            runner: ubuntu-24.04-arm
            rust_target: aarch64-unknown-linux-gnu
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.rust_target }}
      - uses: Swatinem/rust-cache@v2

      - name: Install cargo-zigbuild
        run: pip3 install ziglang cargo-zigbuild

      - name: Build native lib (targeting glibc 2.17 for ES compat)
        run: |
          cargo zigbuild --release -p kazsearch-elastic --target ${{ matrix.rust_target }}.2.17
          mkdir -p elastic/java/src/main/resources/native/linux-${{ matrix.arch }}
          cp target/${{ matrix.rust_target }}/release/libkazsearch_elastic.so elastic/java/src/main/resources/native/linux-${{ matrix.arch }}/
          # Verify glibc version requirement is low enough
          objdump -T target/${{ matrix.rust_target }}/release/libkazsearch_elastic.so | grep GLIBC | sed 's/.*GLIBC_/GLIBC_/' | sort -Vu || true

      - uses: actions/upload-artifact@v4
        with:
          name: es-native-${{ matrix.arch }}
          path: elastic/java/src/main/resources/native/linux-${{ matrix.arch }}/libkazsearch_elastic.so

  bundle-es-plugin:
    name: Bundle ES plugin ZIP (ES ${{ matrix.es }})
    needs: build-es-plugin
    runs-on: ubuntu-latest
    strategy:
      matrix:
        # One zip per supported ES version: classic plugins install only on
        # the exact version stamped in plugin-descriptor.properties. Latest
        # patch of each supported minor, plus 8.17.0 (our docker/dev pin).
        es: ["8.17.0", "8.17.10", "8.18.8", "8.19.18"]
    steps:
      - uses: actions/checkout@v4

      - uses: actions/download-artifact@v4
        with:
          pattern: es-native-*
          path: /tmp/es-natives

      - name: Arrange native libs
        run: |
          cd elastic/java/src/main/resources/native
          for arch in x86_64 aarch64; do
            mkdir -p linux-${arch}
            if [ -d /tmp/es-natives/es-native-${arch} ]; then
              cp /tmp/es-natives/es-native-${arch}/*.so linux-${arch}/
            fi
          done
          find . -name '*.so' -ls

      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 21

      - uses: gradle/actions/setup-gradle@v4

      - name: Build and verify plugin ZIP
        working-directory: elastic/java
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          gradle test bundlePlugin -PesVersion=${{ matrix.es }}
          echo "=== Built artifacts ==="
          ls -la build/distributions/
          zip="build/distributions/analysis-kazsearch-${VERSION}-es${{ matrix.es }}.zip"
          [ -f "$zip" ] || { echo "::error::expected $zip (version mismatch between tag and build.gradle?)"; exit 1; }
          unzip -l "$zip"
          for want in "linux-x86_64/libkazsearch_elastic.so" "linux-aarch64/libkazsearch_elastic.so" \
                      "data/kaz_stems.dict" "data/kaz_stems.dict.verbs"; do
            unzip -l "$zip" | grep -q "$want" || { echo "::error::plugin zip missing $want"; exit 1; }
          done
          unzip -p "$zip" plugin-descriptor.properties | grep -qx "version=${VERSION}" \
            || { echo "::error::descriptor version not stamped"; exit 1; }
          unzip -p "$zip" plugin-descriptor.properties | grep -qx "elasticsearch.version=${{ matrix.es }}" \
            || { echo "::error::descriptor elasticsearch.version not stamped"; exit 1; }

      - uses: actions/upload-artifact@v4
        with:
          name: es-plugin-${{ matrix.es }}
          path: elastic/java/build/distributions/analysis-kazsearch-*.zip

  release:
    name: GitHub Release
    needs: [build-deb, bundle-es-plugin]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/download-artifact@v4
        with:
          path: artifacts
          merge-multiple: true

      - name: Create GitHub Release
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release create ${{ github.ref_name }} \
            --title "pg_kazsearch ${{ github.ref_name }}" \
            --generate-notes \
            artifacts/*.deb artifacts/*.zip

  docker:
    name: Docker / PG ${{ matrix.pg }}
    needs: build-deb
    runs-on: ubuntu-latest
    strategy:
      matrix:
        pg: [16, 17, 18]
    steps:
      - uses: actions/checkout@v4

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - uses: docker/setup-buildx-action@v3

      - uses: actions/download-artifact@v4
        with:
          pattern: deb-pg${{ matrix.pg }}
          path: debs
          merge-multiple: true

      - name: Build and push image
        uses: docker/build-push-action@v6
        with:
          context: .
          file: docker/Dockerfile.release
          build-args: |
            PG_MAJOR=${{ matrix.pg }}
          push: true
          platforms: linux/amd64
          tags: |
            ghcr.io/${{ github.repository }}:${{ matrix.pg }}-${{ github.ref_name }}
            ghcr.io/${{ github.repository }}:${{ matrix.pg }}
            ${{ matrix.pg == '18' && format('ghcr.io/{0}:latest', github.repository) || '' }}

  pgxn:
    name: Publish to PGXN
    needs: build-deb
    runs-on: ubuntu-latest
    container: pgxn/pgxn-tools
    env:
      PGXN_USERNAME: ${{ secrets.PGXN_USERNAME }}
      PGXN_PASSWORD: ${{ secrets.PGXN_PASSWORD }}
    steps:
      - uses: actions/checkout@v4

      - name: Generate META.json
        run: |
          version=${GITHUB_REF_NAME#v}
          sed "s/@PGXN_VERSION@/${version}/g" META.json.in > META.json

      - name: Bundle and publish
        env:
          GIT_BUNDLE_OPTS: --add-file META.json
        run: pgxn-bundle && pgxn-release
