name: build-docker-containers

on:
  workflow_dispatch:
    inputs:
      build:
        description: "Build and publish native multi-arch image"
        type: boolean
        default: true
      update_readme:
        description: "Also sync DockerHub README"
        type: boolean
        default: false
  push:
    branches:
      - dev
      - master
      - testing
    tags:
      - "v*"
    paths-ignore:
      - "**.md"
      - ".github/ISSUE_TEMPLATE/**"

env:
  REGISTRY: ${{ vars.REGISTRY != '' && vars.REGISTRY || '' }}
  DOCKERHUB_SLUG: arabcoders/ytptube
  GHCR_SLUG: ghcr.io/arabcoders/ytptube
  PNPM_VERSION: 10
  NODE_VERSION: 20
  PYTHON_VERSION: "3.13"

jobs:
  test:
    name: Run Tests & Prepare Frontend
    permissions:
      contents: read
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}

      - name: Install uv
        run: pip install uv

      - name: Install Python dependencies
        run: uv sync

      - name: Run Python linting
        run: uv run ruff check app/

      - name: Run Python tests
        run: uv run pytest app/tests/ -v --tb=short

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: pnpm
          cache-dependency-path: "ui/pnpm-lock.yaml"

      - name: Install frontend dependencies
        working-directory: ui
        env:
          NODE_ENV: development
        run: pnpm install --frozen-lockfile

      - name: Prepare frontend (nuxt prepare)
        working-directory: ui
        env:
          NODE_ENV: development
        run: pnpm nuxt prepare

      - name: Run frontend linting
        working-directory: ui
        run: pnpm run lint

      - name: Run frontend type checking
        working-directory: ui
        run: pnpm run typecheck

      - name: Run frontend tests
        working-directory: ui
        run: pnpm run test:ci

      - name: Remove dev dependencies
        working-directory: ui
        env:
          NODE_ENV: production
        run: pnpm install --frozen-lockfile --prod

      - name: Build frontend
        working-directory: ui
        run: pnpm run generate

      - name: Install GitPython
        run: pip install gitpython

      - name: Generate CHANGELOG.json
        run: |
          python3 .github/scripts/generate_changelog.py -p . -f ./ui/exported/CHANGELOG.json

      - name: Upload frontend build
        uses: actions/upload-artifact@v4
        if: env.REGISTRY == ''
        with:
          name: frontend-build
          path: ui/exported/
          retention-days: 1

  docker-build-arch:
    name: Build Container (${{ matrix.arch }})
    runs-on: ${{ matrix.os }}
    needs: [test]
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            arch: amd64
          - os: ubuntu-latest
            arch: arm64
    permissions:
      packages: write
      contents: write
    env:
      ARCH: ${{ matrix.arch }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Download frontend build
        uses: actions/download-artifact@v4
        if: env.REGISTRY == ''
        with:
          name: frontend-build
          path: ui/exported/

      - name: Update app/library/version.py
        run: |
          VERSION="${GITHUB_REF##*/}"
          SHA=$(git rev-parse HEAD)
          DATE=$(date -u +"%Y%m%d")
          BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | sed 's/\//-/g')

          echo "APP_VERSION=${VERSION}" >> "$GITHUB_ENV"
          echo "APP_SHA=${SHA}" >> "$GITHUB_ENV"
          echo "APP_DATE=${DATE}" >> "$GITHUB_ENV"
          echo "APP_BRANCH=${BRANCH}" >> "$GITHUB_ENV"

          sed -i \
            -e "s/^APP_VERSION = \".*\"/APP_VERSION = \"${VERSION}\"/" \
            -e "s/^APP_COMMIT_SHA = \".*\"/APP_COMMIT_SHA = \"${SHA}\"/" \
            -e "s/^APP_BUILD_DATE = \".*\"/APP_BUILD_DATE = \"${DATE}\"/" \
            -e "s/^APP_BRANCH = \".*\"/APP_BRANCH = \"${BRANCH}\"/" \
            app/library/version.py

          echo "Updated version info:"
          cat app/library/version.py

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            name=${{ env.DOCKERHUB_SLUG }},enable=${{ env.REGISTRY == '' }}
            name=${{ env.GHCR_SLUG }},enable=${{ env.REGISTRY == '' }}
            name=${{ env.REGISTRY }}/${{ github.repository }},enable=${{ env.REGISTRY != '' }}
          flavor: |
            latest=false
            suffix=-${{ env.ARCH }}
          tags: |
            type=ref,event=branch
            type=ref,event=tag
            type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}

      - name: Login to Private Registry
        uses: docker/login-action@v3
        if: env.REGISTRY != ''
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GIT_TOKEN && secrets.GIT_TOKEN || secrets.GITHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        if: env.REGISTRY == ''
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Login to DockerHub
        uses: docker/login-action@v3
        if: env.REGISTRY == ''
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

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

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/${{ env.ARCH }}
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-to: type=gha,mode=max,scope=${{ github.workflow }}
          cache-from: type=gha,scope=${{ github.workflow }}
          provenance: true

  docker-publish-manifest:
    name: Publish multi-arch manifest
    needs: [docker-build-arch]
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: write
    steps:
      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            name=${{ env.DOCKERHUB_SLUG }},enable=${{ env.REGISTRY == '' }}
            name=${{ env.GHCR_SLUG }},enable=${{ env.REGISTRY == '' }}
            name=${{ env.REGISTRY }}/${{ github.repository }},enable=${{ env.REGISTRY != '' }}
          flavor: |
            latest=false
          tags: |
            type=ref,event=branch
            type=ref,event=tag
            type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}

      - name: Login to Private Registry
        uses: docker/login-action@v3
        if: env.REGISTRY != ''
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GIT_TOKEN && secrets.GIT_TOKEN || secrets.GITHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        if: env.REGISTRY == ''
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Login to DockerHub
        uses: docker/login-action@v3
        if: env.REGISTRY == ''
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Create and push manifest list
        shell: bash
        run: |
          IFS=$'\n'
          for tag in $(echo "${{ steps.meta.outputs.tags }}"); do
            echo "Creating manifest for ${tag}"
            docker buildx imagetools create \
              --tag "$tag" \
              "${tag}-amd64" \
              "${tag}-arm64"
          done

      - name: Overwrite GitHub release notes
        if: startsWith(github.ref, 'refs/tags/v') && env.REGISTRY == ''
        uses: actions/github-script@v6
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const tag = context.ref.replace('refs/tags/', '');

            const latestRelease = await github.rest.repos.listReleases({
              owner: context.repo.owner,
              repo: context.repo.repo,
            });

            const release = latestRelease.data.find(r => r.tag_name === tag);
            if (!release) {
              core.setFailed(`Release with tag ${tag} not found`);
              return;
            }

            const { data: comparison } = await github.rest.repos.compareCommits({
              owner: context.repo.owner,
              repo: context.repo.repo,
              base: latestRelease.data[1]?.tag_name || '',
              head: tag,
            });

            const commits = comparison.commits.filter(c => 1 === c.parents.length);

            const changelog = commits.map(
              c => `- ${c.sha.substring(0, 7)} ${c.commit.message.split('\n')[0]} by @${c.commit.author.name}`
            ).join('\n');

            if (!changelog) {
              core.setFailed('No commits found for the changelog');
              return;
            }

            console.log(`Changelog for ${tag}:\n${changelog}`);

            await github.rest.repos.updateRelease({
              owner: context.repo.owner,
              repo: context.repo.repo,
              release_id: release.id,
              body: changelog
            });

  dockerhub-sync-readme:
    name: DockerHub README sync
    runs-on: ubuntu-latest
    permissions:
      contents: read
    if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || (github.event_name == 'workflow_dispatch' && github.event.inputs.update_readme == 'true')
    steps:
      - name: Sync README
        uses: docker://lsiodev/readme-sync:latest
        if: env.REGISTRY == ''
        env:
          DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
          DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
          GIT_REPOSITORY: ${{ github.repository }}
          DOCKER_REPOSITORY: ${{ env.DOCKERHUB_SLUG }}
          GIT_BRANCH: master
        with:
          entrypoint: node
          args: /opt/docker-readme-sync/sync
