name: CI/CD

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

concurrency:
  group: hamkadr-cicd-${{ github.ref }}
  cancel-in-progress: true

# ─────────────────────────────────────────────────────────────────────────────
# Runner labels (act_runner):
#   ubuntu-latest:docker://node:20-alpine   ← CI jobs run in real Docker containers
#   self-hosted:host                        ← deploy runs directly on the server
# All images/packages via Nexus at mirror.soroushasadi.com.
# Required Gitea secret: ENV_FILE (contents of .env)
# ─────────────────────────────────────────────────────────────────────────────

jobs:

  # ── CI: compile-check (every push / PR) ──────────────────────────────────────
  ci:
    name: "CI · dotnet build"
    runs-on: ubuntu-latest
    container:
      image: mirror.soroushasadi.com/dotnet/sdk:10.0
      options: --add-host=gitea:host-gateway
    steps:
      - name: Checkout
        env:
          TOKEN: ${{ github.token }}
          REF:   ${{ github.ref }}
        run: |
          git init
          git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
          git config http.extraheader "Authorization: Bearer ${TOKEN}"
          git fetch --depth=1 origin "${REF}"
          git checkout FETCH_HEAD

      - name: Write NuGet config (Liara primary; Nexus optional)
        # NOTE: mirror.soroushasadi.com currently serves an incomplete TLS chain
        # (leaf only, no intermediate). .NET on Linux does NOT auto-fetch the
        # intermediate via AIA the way Windows does, so it fails with PartialChain.
        # Liara serves a complete chain, so it is the deterministic source here.
        # Re-add Nexus once nginx points ssl_certificate at fullchain.pem.
        run: |
          cat > /tmp/nuget.ci.config << 'EOF'
          <?xml version="1.0" encoding="utf-8"?>
          <configuration>
            <packageSources>
              <clear />
              <add key="liara"
                   value="https://package-mirror.liara.ir/repository/nuget/index.json"
                   protocolVersion="3" />
            </packageSources>
            <config>
              <add key="http_retry_count" value="6" />
              <add key="http_retry_delay_milliseconds" value="1000" />
            </config>
          </configuration>
          EOF

      - name: Restore
        # NuGetAudit=false: the audit pings api.nuget.org for CVE data, which is
        # filtered in Iran (100s timeout + NU1900 noise). The mirror has the packages.
        run: dotnet restore src/JobsMedical.Web/JobsMedical.Web.csproj --configfile /tmp/nuget.ci.config -p:NuGetAudit=false
        env:
          DOTNET_CLI_TELEMETRY_OPTOUT: 1

      - name: Build
        run: dotnet build src/JobsMedical.Web/JobsMedical.Web.csproj --no-restore -c Release -p:NuGetAudit=false

  # ── CD: build image → deploy on the server (push to main only) ────────────────
  deploy:
    name: "Deploy · hamkadr"
    runs-on: self-hosted
    env:
      # act host runner starts with a minimal PATH — extend so docker/snap resolve.
      PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
    needs: [ci]
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    timeout-minutes: 30

    steps:
      - name: Checkout
        env:
          TOKEN: ${{ github.token }}
          REF:   ${{ github.ref }}
        run: |
          git init
          git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
          git config http.extraheader "Authorization: Bearer ${TOKEN}"
          git fetch --depth=1 origin "${REF}"
          git checkout FETCH_HEAD

      - name: Write .env
        run: printf '%s' "$ENV_FILE" > .env
        env:
          ENV_FILE: ${{ secrets.ENV_FILE }}

      - name: Tag current image for rollback (before rebuild)
        run: |
          if docker image inspect mirror.soroushasadi.com/hamkadr/api:latest >/dev/null 2>&1; then
            docker tag mirror.soroushasadi.com/hamkadr/api:latest mirror.soroushasadi.com/hamkadr/api:rollback
            echo "Tagged previous image → :rollback"
          else
            echo "No previous image — first deploy."
          fi

      - name: Back up database (if running)
        run: |
          set -a; . ./.env; set +a
          if docker ps -q --filter name='^hamkadr_db$' | grep -q .; then
            BACKUP_DIR="/opt/hamkadr-backups"; mkdir -p "$BACKUP_DIR"
            STAMP=$(date +%Y%m%d-%H%M%S)
            docker exec hamkadr_db pg_dump -U "${POSTGRES_USER:-hamkadr}" "${POSTGRES_DB:-hamkadr}" \
              > "$BACKUP_DIR/hamkadr-$STAMP.sql" \
              && echo "✅ DB backed up → $BACKUP_DIR/hamkadr-$STAMP.sql" \
              || echo "⚠️  backup failed (non-fatal)"
            ls -t "$BACKUP_DIR"/*.sql 2>/dev/null | tail -n +11 | xargs -r rm
          else
            echo "ℹ️  hamkadr_db not running — first deploy, nothing to back up."
          fi

      - name: Build image
        run: docker compose build api
        env:
          DOCKER_BUILDKIT: 1
          COMPOSE_DOCKER_CLI_BUILD: 1

      - name: Start database
        run: docker compose up -d --no-deps db

      - name: Deploy app (stop + rm + up — reliable across docker versions)
        run: |
          docker stop hamkadr_api 2>/dev/null || true
          docker rm   hamkadr_api 2>/dev/null || true
          docker compose up -d --no-deps api

      - name: Wait for healthy
        run: |
          echo "Waiting for hamkadr_api (up to 3 min)..."
          for i in $(seq 1 36); do
            HEALTH=$(docker inspect --format='{{.State.Health.Status}}' hamkadr_api 2>/dev/null || echo "missing")
            STATE=$(docker inspect  --format='{{.State.Status}}'        hamkadr_api 2>/dev/null || echo "missing")
            RESTARTS=$(docker inspect --format='{{.RestartCount}}'      hamkadr_api 2>/dev/null || echo "0")
            echo "  [$i/36] state=$STATE health=$HEALTH restarts=$RESTARTS"
            [ "$HEALTH" = "healthy" ] && echo "✅ hamkadr_api healthy" && exit 0
            if [ "$STATE" = "exited" ] || [ "$STATE" = "dead" ]; then
              echo "❌ hamkadr_api crashed — logs:"; docker logs hamkadr_api 2>&1 | tail -120; exit 1
            fi
            if [ "$RESTARTS" -gt 1 ]; then
              echo "❌ hamkadr_api crash-loop — logs:"; docker logs hamkadr_api 2>&1 | tail -120; exit 1
            fi
            [ "$i" = "36" ] && echo "❌ timeout" && docker logs hamkadr_api 2>&1 | tail -80 && exit 1
            sleep 5
          done

      - name: Show containers
        if: always()
        run: docker compose ps

      - name: Prune old hamkadr images
        if: success()
        # Only untagged (<none>) hamkadr images — never touches other projects.
        run: |
          docker images --format '{{.Repository}}:{{.Tag}} {{.ID}}' \
            | grep '^mirror\.soroushasadi\.com/hamkadr/' \
            | grep '<none>' \
            | awk '{print $2}' \
            | xargs -r docker rmi || true
