name: CI

"on":
  push:
    branches: [main]
    tags-ignore: ["v*"]
    paths-ignore: ["README.md"]
  pull_request:
    branches: [main]
    paths-ignore: ["README.md"]

env:
  CARGO_TERM_COLOR: always
  BINARY_NAME: passhrs

jobs:
  lint:
    name: Lint & fmt
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: lint-cargo-${{ hashFiles('Cargo.lock') }}

      - name: Check formatting
        run: cargo fmt --all -- --check

      - name: Clippy
        run: cargo clippy --all-targets -- -D warnings

  unit-tests:
    name: Unit tests (${{ matrix.name }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: ubuntu-24.04
            os: ubuntu-24.04
            target: x86_64-unknown-linux-gnu
          - name: macos-14
            os: macos-14
            target: aarch64-apple-darwin
          - name: windows-msvc
            os: windows-2022
            target: x86_64-pc-windows-msvc
          - name: windows-gnu
            os: windows-2022
            target: x86_64-pc-windows-gnu

    steps:
      - uses: actions/checkout@v4

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

      - name: Install NASM (windows only)
        if: matrix.name == 'windows-msvc'
        uses: ilammy/setup-nasm@v1

      - name: Install NASM (windows gnu)
        if: matrix.name == 'windows-gnu'
        uses: ilammy/setup-nasm@v1

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ matrix.name }}-cargo-${{ hashFiles('Cargo.lock') }}

      - name: Build
        run: cargo build --release --target ${{ matrix.target }}

      - name: Copy binary for tests
        shell: bash
        run: |
          mkdir -p target/release 2>/dev/null || true
          cp target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}${{ (contains(matrix.target, 'windows') && '.exe' || '') }} target/release/${{ env.BINARY_NAME }}${{ (contains(matrix.target, 'windows') && '.exe' || '') }}

      - name: Run tests
        run: cargo test --release

      - name: Upload binary
        uses: actions/upload-artifact@v4
        with:
          name: ${{ env.BINARY_NAME }}-${{ matrix.target }}
          path: target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}${{ (contains(matrix.target, 'windows') && '.exe' || '') }}
          if-no-files-found: warn

  integration-tests:
    name: Integration tests
    runs-on: ubuntu-24.04
    needs: unit-tests
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: integration-cargo-${{ hashFiles('Cargo.lock') }}

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

      - name: Build & start Docker SSH container
        run: |
          docker build -t phr-test-ssh-image tests/container/
          docker run -d --name phr-test-ssh -p 22222:22 phr-test-ssh-image
          sleep 3

      - name: Run integration tests
        run: cargo test --release -- --include-ignored

      - name: Cleanup
        if: always()
        run: docker rm -f phr-test-ssh 2>/dev/null || true

  cross-linux:
    name: Cross-compile (${{ matrix.target }})
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        target:
          - aarch64-unknown-linux-gnu
          - aarch64-unknown-linux-musl
    steps:
      - uses: actions/checkout@v4

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

      - name: Install cross
        run: cargo install cross --git https://github.com/cross-rs/cross

      - name: Build
        run: cross build --release --target ${{ matrix.target }}

      - name: Upload binary
        uses: actions/upload-artifact@v4
        with:
          name: ${{ env.BINARY_NAME }}-${{ matrix.target }}
          path: target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}
          if-no-files-found: warn

  docker:
    name: Docker multi-arch build & push (main)
    needs: [unit-tests, cross-linux]
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: x86_64-unknown-linux-musl

      - name: Install musl tools
        run: sudo apt-get install -y musl-tools

      - name: Build x86_64 musl binary
        run: cargo build --release --target x86_64-unknown-linux-musl

      - name: Stage x86_64 binary
        run: |
          mkdir -p bin/amd64
          cp target/x86_64-unknown-linux-musl/release/${{ env.BINARY_NAME }} bin/amd64/${{ env.BINARY_NAME }}

      - name: Download aarch64-gnu artifact
        uses: actions/download-artifact@v4
        with:
          name: ${{ env.BINARY_NAME }}-aarch64-unknown-linux-gnu
          path: artifacts-gnu

      - name: Download aarch64-musl artifact
        uses: actions/download-artifact@v4
        with:
          name: ${{ env.BINARY_NAME }}-aarch64-unknown-linux-musl
          path: artifacts-musl

      - name: Stage aarch64 binary (prefer musl)
        run: |
          mkdir -p bin/arm64
          if [ -f artifacts-musl/${{ env.BINARY_NAME }} ]; then
            cp artifacts-musl/${{ env.BINARY_NAME }} bin/arm64/${{ env.BINARY_NAME }}
          else
            cp artifacts-gnu/${{ env.BINARY_NAME }} bin/arm64/${{ env.BINARY_NAME }}
          fi
          chmod +x bin/arm64/${{ env.BINARY_NAME }}
          ls -la bin/amd64/ bin/arm64/

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

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

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ secrets.DOCKER_USERNAME }}/${{ env.BINARY_NAME }}
          tags: |
            type=raw,value=main

      - name: Build multi-arch (no push, no login)
        uses: docker/build-push-action@v6
        with:
          context: .
          file: Dockerfile.static
          platforms: linux/amd64,linux/arm64
          push: false
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          provenance: false
          sbom: false
          cache-from: type=gha
          cache-to: type=gha,mode=max
        env:
          DOCKER_BUILD_RECORD_UPLOAD: false

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Push (login required)
        uses: docker/build-push-action@v6
        with:
          context: .
          file: Dockerfile.static
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          provenance: false
          sbom: false
          cache-from: type=gha
          cache-to: type=gha,mode=max
        env:
          DOCKER_BUILD_RECORD_UPLOAD: false
