name: release

on:
  push:
    tags: ["v*"]
  workflow_dispatch:

env:
  CARGO_TERM_COLOR: always

jobs:
  audit:
    name: Security Audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo-audit
        uses: actions/cache@v5
        with:
          path: ~/.cargo/bin/cargo-audit
          key: cargo-audit-0.21

      - name: Install cargo-audit
        run: command -v cargo-audit || cargo install cargo-audit --locked

      - name: Run security audit
        run: cargo audit

  build:
    name: Build ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          # Linux x86_64 (static musl — portable across glibc versions; also the
          # amd64 container binary)
          - target: x86_64-unknown-linux-musl
            os: ubuntu-latest
            cross: false
          # Linux aarch64 (static musl — the arm64 container binary)
          - target: aarch64-unknown-linux-musl
            os: ubuntu-latest
            cross: true
          # Linux aarch64 (glibc)
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
            cross: true
          # macOS aarch64 (Apple Silicon)
          - target: aarch64-apple-darwin
            os: macos-latest
            cross: false
          # macOS x86_64 (Intel)
          - target: x86_64-apple-darwin
            os: macos-latest
            cross: false
          # Windows x86_64
          - target: x86_64-pc-windows-msvc
            os: windows-latest
            cross: false

    steps:
      - uses: actions/checkout@v7

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Cache cargo registry and git
        uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-

      - name: Install cross
        if: matrix.cross
        run: cargo install cross --version 0.2.5

      - name: Install musl-tools
        if: ${{ !matrix.cross && contains(matrix.target, 'musl') }}
        run: sudo apt-get update && sudo apt-get install -y musl-tools

      # The default feature set IS the canonical single binary (clipboard, http,
      # keyring, updates) — every platform ships the same features, no variants.
      # `keyring-secret-service` (D-Bus) stays off so the musl build links static.
      - name: Build (native)
        if: ${{ !matrix.cross }}
        run: cargo build --release --locked --target ${{ matrix.target }}

      - name: Build (cross)
        if: matrix.cross
        run: cross build --release --locked --target ${{ matrix.target }}

      - name: Package (Unix)
        if: runner.os != 'Windows'
        shell: bash
        run: |
          cd target/${{ matrix.target }}/release
          tar czvf ../../../nbox-${{ matrix.target }}.tar.gz nbox
          cd -

      - name: Package (Windows)
        if: runner.os == 'Windows'
        shell: bash
        run: |
          cd target/${{ matrix.target }}/release
          7z a ../../../nbox-${{ matrix.target }}.zip nbox.exe >/dev/null
          cd -

      - name: Upload artifact
        uses: actions/upload-artifact@v7
        with:
          name: nbox-${{ matrix.target }}
          path: |
            nbox-${{ matrix.target }}.tar.gz
            nbox-${{ matrix.target }}.zip
          if-no-files-found: ignore

  completions:
    name: Generate Shell Completions and Man Page
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo registry and git
        uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-

      - name: Build
        run: cargo build --release --locked

      - name: Generate completions and man pages
        run: |
          mkdir -p completions
          ./target/release/nbox completions bash > completions/nbox.bash
          ./target/release/nbox completions zsh > completions/_nbox
          ./target/release/nbox completions fish > completions/nbox.fish
          ./target/release/nbox completions powershell > completions/_nbox.ps1
          ./target/release/nbox completions elvish > completions/nbox.elv
          # Full man-page set: top-level nbox.1 plus one page per (sub)command
          # named for the full invocation (nbox-device.1, nbox-config-init.1,
          # nbox-profile-add.1, …), so `man nbox-device` resolves after install.
          ./target/release/nbox man completions/man
          ls -1 completions/man
          tar czvf nbox-completions.tar.gz completions/

      - name: Upload completions
        uses: actions/upload-artifact@v7
        with:
          name: nbox-completions
          path: nbox-completions.tar.gz

  docker:
    name: Publish Container Image (GHCR)
    needs: [audit, build]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v7

      - name: Download Linux musl artifacts
        uses: actions/download-artifact@v8
        with:
          pattern: nbox-*-linux-musl
          path: artifacts

      - name: Stage binaries by Docker architecture
        run: |
          mkdir -p bin/amd64 bin/arm64
          tar xzf artifacts/nbox-x86_64-unknown-linux-musl/nbox-x86_64-unknown-linux-musl.tar.gz -C bin/amd64
          tar xzf artifacts/nbox-aarch64-unknown-linux-musl/nbox-aarch64-unknown-linux-musl.tar.gz -C bin/arm64

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Log in to GHCR
        uses: docker/login-action@v4
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract image metadata
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
          labels: |
            org.opencontainers.image.description=Terminal UI and CLI for NetBox — fast search, IPAM lookups, and device context

      - name: Build and push image
        uses: docker/build-push-action@v7
        with:
          context: .
          file: Dockerfile.release
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  release:
    name: Create Release
    needs: [audit, build, completions]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v7

      - name: Download all artifacts
        uses: actions/download-artifact@v8
        with:
          path: artifacts

      - name: List artifacts
        run: find artifacts -type f

      - name: Collect assets and generate checksums
        run: |
          mkdir -p dist
          find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" \) -exec mv {} dist/ \;
          cd dist
          sha256sum -- * > SHA256SUMS
          cat SHA256SUMS
          cd -

      - name: Extract release notes from CHANGELOG
        run: |
          version="${GITHUB_REF_NAME#v}"
          # Pull the section between "## [<version>]" and the next "## [" heading,
          # so the published notes are the curated changelog — not a bare PR list.
          awk -v ver="$version" '
            $0 ~ "^## \\[" ver "\\]" {f=1; next}
            f && /^## \[/ {exit}
            f {print}
          ' CHANGELOG.md > release-notes.md
          # Drop the leading blank line(s) after the heading.
          sed -i -e '/./,$!d' release-notes.md
          if [ ! -s release-notes.md ]; then
            echo "::warning::no CHANGELOG entry for ${version}; using auto-generated notes only"
            printf 'Release %s.\n' "${version}" > release-notes.md
          fi
          echo "----- release notes -----"
          cat release-notes.md

      - name: Create Release
        uses: softprops/action-gh-release@v3
        with:
          files: |
            dist/*
          # Curated CHANGELOG section as the body; GitHub appends its auto-generated
          # "What's Changed" PR list + full-changelog link below it.
          body_path: release-notes.md
          generate_release_notes: true
          draft: false
          prerelease: ${{ contains(github.ref, '-rc') || contains(github.ref, '-beta') || contains(github.ref, '-alpha') }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
