name: CI

on:
  push:
    branches:
      - main
      - beta
      - develop
    paths-ignore:
      - "**/CHANGELOG.md"
  pull_request:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}
  cancel-in-progress: true

jobs:
  api-lint:
    strategy:
      fail-fast: false
      matrix:
        suite: [ordinals]
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./api/${{ matrix.suite }}
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: 'api/${{ matrix.suite }}/.nvmrc'

      - name: Cache node modules
        uses: actions/cache@v4
        env:
          cache-name: cache-node-modules
        with:
          path: |
            ~/.npm
            **/node_modules
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache-name }}-
            ${{ runner.os }}-build-
            ${{ runner.os }}-

      - name: Install deps
        run: npm ci --audit=false

      - name: Lint ESLint
        run: npm run lint:eslint

      - name: Lint Prettier
        run: npm run lint:prettier

      - name: Lint Unused Exports
        run: npm run lint:unused-exports

  api-test:
    strategy:
      fail-fast: false
      matrix:
        suite: [ordinals]
    defaults:
      run:
        working-directory: ./api/${{ matrix.suite }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: 'api/${{ matrix.suite }}/.nvmrc'

      - name: Cache node modules
        uses: actions/cache@v4
        env:
          cache-name: cache-node-modules
        with:
          path: |
            ~/.npm
            **/node_modules
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache-name }}-
            ${{ runner.os }}-build-
            ${{ runner.os }}-

      - name: Install deps
        run: npm ci --audit=false

      - name: Setup integration environment
        run: |
          sudo ufw disable
          npm run testenv:run -- -d
          npm run testenv:logs -- --no-color &> docker-compose-logs.txt &

      - name: Run tests
        run: npm run test -- --coverage

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

      - name: Print integration environment logs
        run: cat docker-compose-logs.txt
        if: failure()

      - name: Teardown integration environment
        run: npm run testenv:stop
        if: always()

  test:
    strategy:
      fail-fast: false
      matrix:
        suite: [ordhook-cli, ordhook-core]
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./components/${{ matrix.suite }}
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false

      - name: Cache cargo
        uses: actions/cache@v3
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            target/
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

      - name: Setup integration environment
        run: |
          sudo ufw disable
          docker compose -f ../../dockerfiles/docker-compose.dev.postgres.yml up -d
          docker compose -f ../../dockerfiles/docker-compose.dev.postgres.yml logs -t -f --no-color &> docker-compose-logs.txt &

      - name: Update Rust
        run: |
          rustup update
          cargo install --force cargo-tarpaulin

      - name: Run tests
        run: |
          cargo tarpaulin --skip-clean --out lcov -- --test-threads=1

      - name: Upload coverage reports to Codecov
        uses: codecov/codecov-action@v4.0.1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          slug: hirosystems/ordhook

      - name: Print integration environment logs
        run: cat docker-compose-logs.txt
        if: failure()

      - name: Teardown integration environment
        run: docker compose -f ../../dockerfiles/docker-compose.dev.postgres.yml down -v -t 0
        if: always()

  semantic-release:
    runs-on: ubuntu-latest
    needs: [api-lint, api-test, test]
    outputs:
      new_release_version: ${{ steps.semantic.outputs.new_release_version }}
      new_release_published: ${{ steps.semantic.outputs.new_release_published }}
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false

      - name: Semantic Release
        uses: cycjimmy/semantic-release-action@v4
        id: semantic
        # Only run on non-PR events or only PRs that aren't from forks
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SEMANTIC_RELEASE_PACKAGE: ${{ github.event.repository.name }}
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_CRATES_IO_API_KEY }}
        with:
          semantic_version: 19
          extra_plugins: |
            @semantic-release/changelog@6.0.3
            @semantic-release/git@10.0.1
            @semantic-release/exec@6.0.3
            conventional-changelog-conventionalcommits@6.1.0

  build-publish:
    runs-on: ubuntu-latest
    needs: semantic-release
    outputs:
      docker_image_digest: ${{ steps.docker_push.outputs.digest }}
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false

      - name: Checkout tag
        if: needs.semantic-release.outputs.new_release_version != ''
        uses: actions/checkout@v4
        with:
          persist-credentials: false
          ref: v${{ needs.semantic-release.outputs.new_release_version }}

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

      - name: Docker Meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            hirosystems/ordhook
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}},value=${{ needs.semantic-release.outputs.new_release_version }},enable=${{ needs.semantic-release.outputs.new_release_version != '' }}
            type=semver,pattern={{major}}.{{minor}},value=${{ needs.semantic-release.outputs.new_release_version }},enable=${{ needs.semantic-release.outputs.new_release_version != '' }}
            type=raw,value=latest,enable={{is_default_branch}}

      - name: Log in to DockerHub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: Build/Push Image
        uses: docker/build-push-action@v5
        id: docker_push
        with:
          context: .
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          file: ./dockerfiles/components/ordhook.dockerfile
          build-args: |
            GIT_COMMIT=${{ env.GITHUB_SHA_SHORT }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          # Only push if (there's a new release on main branch, or if building a non-main branch) and (Only run on non-PR events or only PRs that aren't from forks)
          push: ${{ (github.ref != 'refs/heads/main' || needs.semantic-release.outputs.new_release_version != '') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}

  api-build-publish:
    strategy:
      fail-fast: false
      matrix:
        suite: [ordinals]
    runs-on: ubuntu-latest
    needs: semantic-release
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}
          fetch-depth: 0
          persist-credentials: false

      - name: Checkout tag
        if: needs.semantic-release.outputs.new_release_version != ''
        uses: actions/checkout@v4
        with:
          persist-credentials: false
          ref: v${{ needs.semantic-release.outputs.new_release_version }}

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

      - name: Docker Meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            hirosystems/${{ matrix.suite }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}},value=${{ needs.semantic-release.outputs.new_release_version }},enable=${{ needs.semantic-release.outputs.new_release_version != '' }}
            type=semver,pattern={{major}}.{{minor}},value=${{ needs.semantic-release.outputs.new_release_version }},enable=${{ needs.semantic-release.outputs.new_release_version != '' }}

      - name: Login to DockerHub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: Build/Tag/Push Image
        uses: docker/build-push-action@v5
        with:
          context: ./api/${{ matrix.suite }}
          file: ./api/${{ matrix.suite }}/Dockerfile
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          # Only push if (there's a new release on main branch, or if building a non-main branch) and (Only run on non-PR events or only PRs that aren't from forks)
          push: ${{ (github.ref != 'refs/heads/master' || needs.semantic-release.outputs.new_release_version != '') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}

  deploy-dev:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        network: [mainnet]
        subenv: [blue]
    needs: build-publish
    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
    env:
      DEPLOY_ENV: dev
    environment:
      name: Development-${{ matrix.network }}-${{ matrix.subenv }}
      url: https://platform.dev.hiro.so/
    steps:
      - name: Checkout actions repo
        uses: actions/checkout@v4
        with:
          ref: main
          token: ${{ secrets.GH_TOKEN }}
          repository: ${{ secrets.DEVOPS_ACTIONS_REPO }}

      - name: Deploy Ordhook build to Dev ${{ matrix.network }} ${{ matrix.subenv }}
        uses: ./actions/deploy
        with:
          docker_image: ${{ env.DOCKER_IMAGE }}
          docker_image_tag_or_digest: ${{ needs.build-publish.outputs.docker_image_digest }}
          file_pattern: manifests/bitcoin/${{ matrix.network }}/ordhook/${{ env.DEPLOY_ENV }}/*/kustomization.yaml
          subenv: ${{ matrix.subenv }}
          gh_token: ${{ secrets.GH_TOKEN }}

  auto-approve-dev:
    runs-on: ubuntu-latest
    if: needs.build-publish.outputs.new_release_published == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
    needs: build-publish
    steps:
      - name: Approve pending deployments
        run: |
          sleep 5
          ENV_ID=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/hirosystems/ordhook/actions/runs/${{ github.run_id }}/pending_deployments" | jq -r '.[0].environment.id // empty')
          if [[ "${ENV_IDS}" != "[]" ]]; then
            curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/hirosystems/ordhook/actions/runs/${{ github.run_id }}/pending_deployments" -d "{\"environment_ids\":[${ENV_ID}],\"state\":\"approved\",\"comment\":\"auto approve\"}"
          fi

  deploy-staging:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        network: [mainnet]
        subenv: [blue]
    needs:
      - build-publish
      - deploy-dev
    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
    env:
      DEPLOY_ENV: stg
    environment:
      name: Staging-${{ matrix.network }}-${{ matrix.subenv }}
      url: https://platform.stg.hiro.so/
    steps:
      - name: Checkout actions repo
        uses: actions/checkout@v4
        with:
          ref: main
          token: ${{ secrets.GH_TOKEN }}
          repository: ${{ secrets.DEVOPS_ACTIONS_REPO }}

      - name: Deploy Ordhook build to Stg ${{ matrix.network }} ${{ matrix.subenv }}
        uses: ./actions/deploy
        with:
          docker_image: ${{ env.DOCKER_IMAGE }}
          docker_image_tag_or_digest: ${{ needs.build-publish.outputs.docker_image_digest }}
          file_pattern: manifests/bitcoin/${{ matrix.network }}/ordhook/${{ env.DEPLOY_ENV }}/*/kustomization.yaml
          subenv: ${{ matrix.subenv }}
          gh_token: ${{ secrets.GH_TOKEN }}

  auto-approve-stg:
    runs-on: ubuntu-latest
    if: needs.build-publish.outputs.new_release_published == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
    needs:
      - build-publish
      - deploy-dev
    steps:
      - name: Approve pending deployments
        run: |
          sleep 5
          ENV_ID=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/hirosystems/ordhook/actions/runs/${{ github.run_id }}/pending_deployments" | jq -r '.[0].environment.id // empty')
          if [[ "${ENV_IDS}" != "[]" ]]; then
            curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/hirosystems/ordhook/actions/runs/${{ github.run_id }}/pending_deployments" -d "{\"environment_ids\":[${ENV_ID}],\"state\":\"approved\",\"comment\":\"auto approve\"}"
          fi

  deploy-prod:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        network: [mainnet]
        subenv: [blue, green]
    needs:
      - build-publish
      - deploy-staging
    if: needs.build-publish.outputs.new_release_published == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
    env:
      DEPLOY_ENV: prd
    environment:
      name: Production-${{ matrix.network }}-${{ matrix.subenv }}
      url: https://platform.hiro.so/
    steps:
      - name: Checkout actions repo
        uses: actions/checkout@v4
        with:
          ref: main
          token: ${{ secrets.GH_TOKEN }}
          repository: ${{ secrets.DEVOPS_ACTIONS_REPO }}

      - name: Deploy Ordhook build to Prd ${{ matrix.network }} ${{ matrix.subenv }}
        uses: ./actions/deploy
        with:
          docker_image: ${{ env.DOCKER_IMAGE }}
          docker_image_tag_or_digest: ${{ needs.build-publish.outputs.docker_image_digest }}
          file_pattern: manifests/bitcoin/${{ matrix.network }}/ordhook/${{ env.DEPLOY_ENV }}/*/kustomization.yaml
          subenv: ${{ matrix.subenv }}
          gh_token: ${{ secrets.GH_TOKEN }}
