name: CI - Build & Verify
run-name: CI ${{ gitea.ref_name }} by @${{ gitea.actor }}

concurrency:
  group: vtubeawards-ci-${{ gitea.ref }}
  cancel-in-progress: true

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:
    inputs:
      no_cache:
        description: 'Build Docker images without cache'
        required: false
        default: false
        type: boolean

jobs:
  verify:
    name: Build, Typecheck & Hygiene
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Reject tracked build output
        run: |
          set -euo pipefail
          tracked="$(git ls-files frontend/dist Backend/bin Backend/obj || true)"
          if [ -n "$tracked" ]; then
            echo "Build output is tracked and must be removed:"
            echo "$tracked"
            exit 1
          fi

      - name: Reject tracked handoff artifacts
        run: |
          set -euo pipefail
          tracked="$(git ls-files '*.zip' '*.docx' 'prototype/**' 'update_v2_docx_*.py' || true)"
          if [ -n "$tracked" ]; then
            echo "Handoff/prototype artifacts are tracked and must be removed:"
            echo "$tracked"
            exit 1
          fi

      - name: Secret pattern scan
        run: |
          set -euo pipefail
          hits="$(
            grep -RInE '(SECRET|TOKEN|PASSWORD|API_KEY)[[:space:]]*[:=][[:space:]]*.{8,}' \
              --include='*.cs' \
              --include='*.ts' \
              --include='*.vue' \
              Backend frontend/src 2>/dev/null || true
          )"
          if [ -n "$hits" ]; then
            echo "Possible hardcoded secret found:"
            echo "$hits"
            exit 1
          fi

      - name: Setup .NET SDK
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '24'

      - name: Backend build
        run: |
          set -euo pipefail
          dotnet restore Backend/Backend.csproj
          dotnet build Backend/Backend.csproj --no-restore --configuration Release

      - name: Frontend build
        run: |
          set -euo pipefail
          base_version="$(node -p "require('./package.json').version")"
          run_number="${GITHUB_RUN_NUMBER:-${GITEA_RUN_NUMBER:-0}}"
          if [ "$run_number" != "0" ]; then
            export VITE_BUILD_VERSION="${base_version}+build.${run_number}"
          else
            export VITE_BUILD_VERSION="${base_version}+local"
          fi
          export VITE_BUILD_DATE="$(date -u +%Y-%m-%d)"
          echo "Building frontend version ${VITE_BUILD_VERSION}"
          npm ci
          npm run build
        working-directory: frontend

  deploy:
    name: Deploy to award.noveria.net
    runs-on: ubuntu-latest
    needs: verify
    timeout-minutes: 30
    if: |
      gitea.event_name == 'workflow_dispatch' ||
      (gitea.event_name == 'push' && gitea.ref == 'refs/heads/main')
    env:
      DEPLOY_PATH: /home/projekte_bao/vtuber-awards
      DEPLOY_APP_PATH: /home/projekte_bao/vtuber-awards/app
      DB_NETWORK: vtuber-awards_internal
      LIVE_URL: https://award.noveria.net
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Resolve deploy metadata
        id: meta
        run: |
          set -euo pipefail
          version="$(node -p "require('./frontend/package.json').version")"
          run_number="${GITHUB_RUN_NUMBER:-${GITEA_RUN_NUMBER:-0}}"
          if [ "$run_number" != "0" ]; then
            build_version="${version}+build.${run_number}"
          else
            build_version="${version}+local"
          fi
          echo "sha=$(git rev-parse HEAD)" >> "$GITEA_OUTPUT"
          echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITEA_OUTPUT"
          echo "version=$version" >> "$GITEA_OUTPUT"
          echo "build_version=$build_version" >> "$GITEA_OUTPUT"
          echo "Deploying version ${build_version}"
          git log -1 --oneline

      - name: Verify production host layout
        run: |
          set -euo pipefail
          docker run --rm \
            -v "${DEPLOY_PATH}:/deploy:ro" \
            alpine:latest \
            sh -lc '
              test -f /deploy/compose.yaml
              test -f /deploy/deploy/backend.Dockerfile
              test -f /deploy/deploy/web.Dockerfile
              test -d /deploy/app
            '

      - name: Sync code to production app directory
        run: |
          set -euo pipefail
          tar --exclude=.git -cf - . | docker run --rm -i \
            -v "${DEPLOY_APP_PATH}:/dest" \
            alpine:latest \
            sh -lc '
              set -e
              dest_owner="$(stat -c "%u:%g" /dest)"
              find /dest -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
              tar -xf - -C /dest
              chown -R "$dest_owner" /dest
            '

      - name: Write frontend build metadata
        run: |
          set -euo pipefail
          build_date="$(date -u +%Y-%m-%d)"
          docker run --rm \
            -e BUILD_VERSION="${{ steps.meta.outputs.build_version }}" \
            -e BUILD_DATE="${build_date}" \
            -v "${DEPLOY_APP_PATH}:/app" \
            alpine:latest \
            sh -lc '
              test -d /app/frontend
              printf "VITE_BUILD_VERSION=%s\nVITE_BUILD_DATE=%s\n" "$BUILD_VERSION" "$BUILD_DATE" > /app/frontend/.env.production
              echo "Frontend build metadata:"
              cat /app/frontend/.env.production
            '

      - name: Build Docker images
        run: |
          set -euo pipefail
          no_cache="${{ gitea.event_name == 'workflow_dispatch' && inputs.no_cache || false }}"
          build_args=""
          if [ "$no_cache" = "true" ]; then
            build_args="--no-cache"
          fi

          docker run --rm \
            -v "${DEPLOY_PATH}:/workspace/vtube-awards" \
            -v /var/run/docker.sock:/var/run/docker.sock \
            -w /workspace/vtube-awards \
            docker:cli \
            sh -lc "docker compose config >/dev/null && docker compose build ${build_args} api web"

      - name: Backup database before migrations
        run: |
          set -euo pipefail
          docker run --rm \
            -v "${DEPLOY_PATH}:/workspace/vtube-awards" \
            -v /var/run/docker.sock:/var/run/docker.sock \
            -w /workspace/vtube-awards \
            docker:cli \
            sh -lc '
              set -e
              mkdir -p backups
              stamp="$(date -u +%Y%m%dT%H%M%SZ)"
              backup="backups/predeploy-${{ steps.meta.outputs.short_sha }}-${stamp}.dump"
              docker compose exec -T postgres sh -lc '"'"'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc'"'"' > "$backup"
              test -s "$backup"
              find backups -name "predeploy-*.dump" -mtime +14 -delete
              echo "Database backup written to $backup"
            '

      - name: Apply EF Core migrations
        run: |
          set -euo pipefail
          docker run --rm \
            -e DEPLOY_APP_PATH="${DEPLOY_APP_PATH}" \
            -e DB_NETWORK="${DB_NETWORK}" \
            -v "${DEPLOY_PATH}:/workspace/vtube-awards" \
            -v /var/run/docker.sock:/var/run/docker.sock \
            -w /workspace/vtube-awards \
            docker:cli \
            sh -lc '
              set -e
              db="$(docker compose exec -T postgres printenv POSTGRES_DB)"
              user="$(docker compose exec -T postgres printenv POSTGRES_USER)"
              password="$(docker compose exec -T postgres printenv POSTGRES_PASSWORD)"
              docker run --rm \
                --network "${DB_NETWORK}" \
                -v "${DEPLOY_APP_PATH}/Backend:/src/Backend" \
                -w /src/Backend \
                -e "VTSA_POSTGRES=Host=postgres;Port=5432;Database=${db};Username=${user};Password=${password}" \
                mcr.microsoft.com/dotnet/sdk:8.0-alpine \
                sh -lc "dotnet tool install --global dotnet-ef >/dev/null && export PATH=\$PATH:/root/.dotnet/tools && dotnet restore >/dev/null && dotnet ef database update"
            '

      - name: Restart production services
        run: |
          set -euo pipefail
          docker run --rm \
            -v "${DEPLOY_PATH}:/workspace/vtube-awards" \
            -v /var/run/docker.sock:/var/run/docker.sock \
            -w /workspace/vtube-awards \
            docker:cli \
            sh -lc 'docker compose up -d --wait --force-recreate api web'

      - name: Verify live health
        run: |
          set -euo pipefail
          retry() {
            label="$1"
            url="$2"
            attempts=10
            wait=3
            for attempt in $(seq 1 "$attempts"); do
              if body="$(curl -fsS --max-time 15 "$url")"; then
                echo "$label OK on attempt $attempt"
                echo "$body"
                return 0
              fi
              echo "$label failed on attempt $attempt/$attempts; waiting ${wait}s"
              sleep "$wait"
            done
            echo "$label failed after $attempts attempts"
            return 1
          }

          retry "API health" "${LIVE_URL}/api/health"
          db_body="$(curl -fsS --max-time 15 "${LIVE_URL}/api/health/database")"
          echo "$db_body"
          echo "$db_body" | grep -q '"canConnect":true'
          echo "$db_body" | grep -q '"pendingMigrations":\[\]'

      - name: Verify frontend assets
        run: |
          set -euo pipefail
          index="$(curl -fsS --max-time 15 "${LIVE_URL}/")"
          js_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.js' | head -n 1)"
          css_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.css' | head -n 1)"
          test -n "$js_asset"
          test -n "$css_asset"
          js_body="$(curl -fsS --max-time 20 "${LIVE_URL}${js_asset}")"
          printf "%s" "$js_body" | grep -Fq "${{ steps.meta.outputs.build_version }}"
          curl -fsS --max-time 20 "${LIVE_URL}${css_asset}" >/dev/null
          echo "Frontend assets served: ${js_asset}, ${css_asset}"
          echo "Frontend version verified: ${{ steps.meta.outputs.build_version }}"

      - name: Show production containers
        if: always()
        run: |
          docker run --rm \
            -v "${DEPLOY_PATH}:/workspace/vtube-awards" \
            -v /var/run/docker.sock:/var/run/docker.sock \
            -w /workspace/vtube-awards \
            docker:cli \
            sh -lc 'docker compose ps'
