name: CI/CD Web + Desktop

on:
  push:
    branches: [ main ]
    tags:
      - 'v*.*.*'
  workflow_dispatch:
    inputs:
      force_web_deploy:
        description: 'Forcar deploy do Web (ignorar filtro)?'
        type: boolean
        required: false
        default: false
      force_convex_deploy:
        description: 'Forcar deploy do Convex (ignorar filtro)?'
        type: boolean
        required: false
        default: false

env:
  APP_DIR: /srv/apps/sistema
  VPS_UPDATES_DIR: /var/www/updates

jobs:
  changes:
    name: Detect changes
    runs-on: [ self-hosted, linux, vps ]
    timeout-minutes: 5
    outputs:
      convex: ${{ steps.filter.outputs.convex }}
      web: ${{ steps.filter.outputs.web }}
    steps:
      - name: Checkout
        uses: https://github.com/actions/checkout@v4
      - name: Paths filter
        id: filter
        uses: https://github.com/dorny/paths-filter@v3
        with:
          filters: |
            convex:
              - 'convex/**'
            web:
              - 'src/**'
              - 'public/**'
              - 'prisma/**'
              - 'next.config.ts'
              - 'package.json'
              - 'bun.lock'
              - 'tsconfig.json'
              - 'middleware.ts'
              - 'stack.yml'

  deploy:
    name: Deploy (VPS Linux)
    needs: changes
    timeout-minutes: 30
    if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' }}
    runs-on: [ self-hosted, linux, vps ]
    steps:
      - name: Checkout
        uses: https://github.com/actions/checkout@v4

      - name: Determine APP_DIR (fallback safe path)
        id: appdir
        run: |
          TS=$(date +%s)
          FALLBACK_DIR="$HOME/apps/web.build.$TS"
          mkdir -p "$FALLBACK_DIR"
          echo "Using APP_DIR (fallback)=$FALLBACK_DIR"
          echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV"

      - name: Setup Bun
        uses: https://github.com/oven-sh/setup-bun@v2
        with:
          bun-version: 1.3.4

      - name: Sync workspace to APP_DIR (preserving local env)
        run: |
          mkdir -p "$EFFECTIVE_APP_DIR"
          RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete"
          EXCLUDE_ENV="--exclude '.env*' --exclude 'apps/desktop/.env*' --exclude 'convex/.env*'"
          if [ "$EFFECTIVE_APP_DIR" != "${APP_DIR:-/srv/apps/sistema}" ]; then
            EXCLUDE_ENV=""
          fi
          rsync $RSYNC_FLAGS \
            --filter='protect .next.old*' \
            --exclude '.next.old*' \
            --filter='protect node_modules' \
            --filter='protect node_modules/**' \
            --filter='protect .pnpm-store' \
            --filter='protect .pnpm-store/**' \
            --filter='protect .env' \
            --filter='protect .env*' \
            --filter='protect apps/desktop/.env*' \
            --filter='protect convex/.env*' \
            --exclude '.git' \
            --exclude '.next' \
            --exclude 'node_modules' \
            --exclude 'node_modules/**' \
            --exclude '.pnpm-store' \
            --exclude '.pnpm-store/**' \
            $EXCLUDE_ENV \
            ./ "$EFFECTIVE_APP_DIR"/

      - name: Acquire Convex admin key
        id: key
        run: |
          echo "Waiting for Convex container..."
          CID=""
          for attempt in $(seq 1 12); do
            CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}')
            if [ -n "$CID" ]; then
              echo "Convex container ready (CID=$CID)"
              break
            fi
            echo "Attempt $attempt/12: container not ready yet; waiting 5s..."
            sleep 5
          done
          CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest"
          if [ -n "$CID" ]; then
            KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1)
          else
            echo "No running convex container detected; attempting offline admin key extraction..."
            VOLUME="sistema_convex_data"
            if docker volume inspect "$VOLUME" >/dev/null 2>&1; then
              KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1)
            else
              echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin"
            fi
          fi
          echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT
          echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)"
          if [ -z "$KEY" ]; then
            echo "ERRO: Nao foi possivel obter a chave admin do Convex"
            docker service ps sistema_convex_backend || true
            exit 1
          fi

      - name: Copy production .env if present
        run: |
          DEFAULT_DIR="${APP_DIR:-/srv/apps/sistema}"
          if [ "$EFFECTIVE_APP_DIR" != "$DEFAULT_DIR" ] && [ -f "$DEFAULT_DIR/.env" ]; then
            echo "Copying production .env from $DEFAULT_DIR to $EFFECTIVE_APP_DIR"
            cp -f "$DEFAULT_DIR/.env" "$EFFECTIVE_APP_DIR/.env"
          fi

      - name: Ensure Next.js cache directory exists and is writable
        run: |
          cd "$EFFECTIVE_APP_DIR"
          mkdir -p .next/cache
          chmod -R u+rwX .next || true

      - name: Cache Next.js build cache (.next/cache)
        uses: https://github.com/actions/cache@v4
        with:
          path: ${{ env.EFFECTIVE_APP_DIR }}/.next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }}-${{ hashFiles('next.config.ts') }}
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }}-
            ${{ runner.os }}-nextjs-

      - name: Lint check (fail fast before build)
        run: |
          cd "$EFFECTIVE_APP_DIR"
          docker run --rm \
            -v "$EFFECTIVE_APP_DIR":/app \
            -w /app \
            sistema_web:node22-bun \
            bash -lc "set -euo pipefail; bun install --frozen-lockfile --filter '!appsdesktop'; bun run lint"

      - name: Install and build (Next.js)
        env:
          PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1"
        run: |
          cd "$EFFECTIVE_APP_DIR"
          docker run --rm \
            -e PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING="$PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING" \
            -e NODE_OPTIONS="--max-old-space-size=4096" \
            -v "$EFFECTIVE_APP_DIR":/app \
            -w /app \
            sistema_web:node22-bun \
            bash -lc "set -euo pipefail; bun install --frozen-lockfile --filter '!appsdesktop'; bun run prisma:generate; bun run build:bun"

      - name: Fix Docker-created file permissions
        run: |
          # Docker cria arquivos como root - corrigir para o usuario runner (UID 1000)
          docker run --rm -v "$EFFECTIVE_APP_DIR":/target alpine:3 \
            chown -R 1000:1000 /target
          echo "Permissoes do build corrigidas"

      - name: Atualizar symlink do APP_DIR estavel (deploy atomico)
        run: |
          set -euo pipefail
          ROOT="$HOME/apps"
          STABLE_LINK="$ROOT/sistema.current"

          mkdir -p "$ROOT"

          # Sanidade: se esses arquivos nao existirem, o container vai falhar no boot.
          test -f "$EFFECTIVE_APP_DIR/scripts/start-web.sh" || { echo "ERROR: scripts/start-web.sh nao encontrado em $EFFECTIVE_APP_DIR" >&2; exit 1; }
          test -f "$EFFECTIVE_APP_DIR/stack.yml" || { echo "ERROR: stack.yml nao encontrado em $EFFECTIVE_APP_DIR" >&2; exit 1; }
          test -d "$EFFECTIVE_APP_DIR/node_modules" || { echo "ERROR: node_modules nao encontrado em $EFFECTIVE_APP_DIR (necessario para next start)" >&2; exit 1; }
          test -d "$EFFECTIVE_APP_DIR/.next" || { echo "ERROR: .next nao encontrado em $EFFECTIVE_APP_DIR (build nao gerado)" >&2; exit 1; }

          PREV=""
          if [ -L "$STABLE_LINK" ]; then
            PREV="$(readlink -f "$STABLE_LINK" || true)"
          fi
          echo "PREV_APP_DIR=$PREV" >> "$GITHUB_ENV"

          ln -sfn "$EFFECTIVE_APP_DIR" "$STABLE_LINK"

          # Compat: mantem $HOME/apps/sistema como symlink quando possivel (nao mexe se for pasta).
          if [ -L "$ROOT/sistema" ] || [ ! -e "$ROOT/sistema" ]; then
            ln -sfn "$STABLE_LINK" "$ROOT/sistema"
          fi

          echo "APP_DIR estavel -> $(readlink -f "$STABLE_LINK")"

      - name: Swarm deploy (stack.yml)
        run: |
          APP_DIR_STABLE="$HOME/apps/sistema.current"
          if [ ! -d "$APP_DIR_STABLE" ]; then
            echo "ERROR: Stable APP_DIR does not exist: $APP_DIR_STABLE" >&2; exit 1
          fi
          cd "$APP_DIR_STABLE"
          set -o allexport
          if [ -f .env ]; then
            echo "Loading .env from $APP_DIR_STABLE"
            . ./.env
          else
            echo "WARNING: No .env found at $APP_DIR_STABLE - stack vars may be empty!"
          fi
          set +o allexport
          echo "Using APP_DIR (stable)=$APP_DIR_STABLE"
          echo "NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL:-<not set>}"
          echo "NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-<not set>}"
          APP_DIR="$APP_DIR_STABLE" RELEASE_SHA=${{ github.sha }} docker stack deploy --with-registry-auth -c stack.yml sistema

      - name: Wait for services to be healthy
        run: |
          echo "Aguardando servicos ficarem saudaveis..."
          for i in $(seq 1 18); do
            WEB_STATUS=$(docker service ls --filter "name=sistema_web" --format "{{.Replicas}}" 2>/dev/null || echo "0/0")
            CONVEX_STATUS=$(docker service ls --filter "name=sistema_convex_backend" --format "{{.Replicas}}" 2>/dev/null || echo "0/0")
            echo "Tentativa $i/18: web=$WEB_STATUS convex=$CONVEX_STATUS"
            if echo "$WEB_STATUS" | grep -q "2/2" && echo "$CONVEX_STATUS" | grep -q "1/1"; then
              echo "Todos os servicos estao saudaveis!"
              exit 0
            fi
            sleep 10
          done
          echo "ERRO: Timeout aguardando servicos. Status atual:"
          docker service ls --filter "label=com.docker.stack.namespace=sistema" || true
          docker service ps sistema_web --no-trunc || true
          docker service logs sistema_web --since 5m --raw 2>/dev/null | tail -n 200 || true

          if [ -n "${PREV_APP_DIR:-}" ]; then
            echo "Rollback: revertendo APP_DIR estavel para: $PREV_APP_DIR"
            ln -sfn "$PREV_APP_DIR" "$HOME/apps/sistema.current"
            cd "$HOME/apps/sistema.current"
            set -o allexport
            if [ -f .env ]; then
              . ./.env
            fi
            set +o allexport
            APP_DIR="$HOME/apps/sistema.current" RELEASE_SHA=${{ github.sha }} docker stack deploy --with-registry-auth -c stack.yml sistema || true
          fi

          exit 1

      - name: Cleanup old build workdirs (keep last 2)
        run: |
          set -e
          ROOT="$HOME/apps"
          KEEP=2
          PATTERN='web.build.*'
          ACTIVE="$(readlink -f "$HOME/apps/sistema.current" 2>/dev/null || true)"
          echo "Scanning $ROOT for old $PATTERN dirs"
          LIST=$(find "$ROOT" -maxdepth 1 -type d -name "$PATTERN" | sort -r || true)
          echo "$LIST" | sed -n "1,${KEEP}p" | sed 's/^/Keeping: /' || true
          echo "$LIST" | sed "1,${KEEP}d" | while read dir; do
            [ -z "$dir" ] && continue
            if [ -n "$ACTIVE" ] && [ "$(readlink -f "$dir")" = "$ACTIVE" ]; then
              echo "Skipping active dir (in use by APP_DIR): $dir"; continue
            fi
            echo "Removing $dir"
            chmod -R u+rwX "$dir" 2>/dev/null || true
            rm -rf "$dir" || {
              echo "Local rm failed, falling back to docker (root) cleanup for $dir..."
              docker run --rm -v "$dir":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true; rm -rf /target/* /target/.[!.]* /target/..?* 2>/dev/null || true' || true
              rm -rf "$dir" 2>/dev/null || rmdir "$dir" 2>/dev/null || true
            }
          done
          echo "Disk usage (top 10 under $ROOT):"
          du -sh "$ROOT"/* 2>/dev/null | sort -rh | head -n 10 || true

  convex_deploy:
    name: Deploy Convex functions
    needs: changes
    timeout-minutes: 20
    if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.convex == 'true' }}
    runs-on: [ self-hosted, linux, vps ]
    env:
      APP_DIR: /srv/apps/sistema
    steps:
      - name: Checkout
        uses: https://github.com/actions/checkout@v4

      - name: Determine APP_DIR (fallback safe path)
        id: appdir
        run: |
          TS=$(date +%s)
          FALLBACK_DIR="$HOME/apps/convex.build.$TS"
          mkdir -p "$FALLBACK_DIR"
          echo "Using APP_DIR (fallback)=$FALLBACK_DIR"
          echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV"

      - name: Sync workspace to APP_DIR (preserving local env)
        run: |
          mkdir -p "$EFFECTIVE_APP_DIR"
          RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete"
          rsync $RSYNC_FLAGS \
            --filter='protect .next.old*' \
            --exclude '.next.old*' \
            --exclude '.env*' \
            --exclude 'apps/desktop/.env*' \
            --exclude 'convex/.env*' \
            --filter='protect node_modules' \
            --filter='protect node_modules/**' \
            --filter='protect .pnpm-store' \
            --filter='protect .pnpm-store/**' \
            --exclude '.git' \
            --exclude '.next' \
            --exclude 'node_modules' \
            --exclude 'node_modules/**' \
            --exclude '.pnpm-store' \
            --exclude '.pnpm-store/**' \
            ./ "$EFFECTIVE_APP_DIR"/

      - name: Acquire Convex admin key
        id: key
        run: |
          echo "Waiting for Convex container..."
          CID=""
          for attempt in $(seq 1 12); do
            CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}')
            if [ -n "$CID" ]; then
              echo "Convex container ready (CID=$CID)"
              break
            fi
            echo "Attempt $attempt/12: container not ready yet; waiting 5s..."
            sleep 5
          done
          CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest"
          if [ -n "$CID" ]; then
            KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1)
          else
            echo "No running convex container detected; attempting offline admin key extraction..."
            VOLUME="sistema_convex_data"
            if docker volume inspect "$VOLUME" >/dev/null 2>&1; then
              KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1)
            else
              echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin"
            fi
          fi
          echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT
          echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)"
          if [ -z "$KEY" ]; then
            echo "ERRO: Nao foi possivel obter a chave admin do Convex"
            docker service ps sistema_convex_backend || true
            exit 1
          fi

      - name: Bring convex.json from live app if present
        run: |
          if [ -f "$APP_DIR/convex.json" ]; then
            echo "Copying $APP_DIR/convex.json -> $EFFECTIVE_APP_DIR/convex.json"
            cp -f "$APP_DIR/convex.json" "$EFFECTIVE_APP_DIR/convex.json"
          else
            echo "No existing convex.json found at $APP_DIR; convex CLI will need self-hosted vars"
          fi

      - name: Set Convex env vars (self-hosted)
        env:
          CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br
          CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }}
          MACHINE_PROVISIONING_SECRET: ${{ secrets.MACHINE_PROVISIONING_SECRET }}
          MACHINE_TOKEN_TTL_MS: ${{ secrets.MACHINE_TOKEN_TTL_MS }}
          FLEET_SYNC_SECRET: ${{ secrets.FLEET_SYNC_SECRET }}
        run: |
          set -e
          docker run --rm -i \
            -v "$EFFECTIVE_APP_DIR":/app \
            -w /app \
            -e CONVEX_SELF_HOSTED_URL \
            -e CONVEX_SELF_HOSTED_ADMIN_KEY \
            -e MACHINE_PROVISIONING_SECRET \
            -e MACHINE_TOKEN_TTL_MS \
            -e FLEET_SYNC_SECRET \
            -e CONVEX_TMPDIR=/app/.convex-tmp \
            node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; export CONVEX_TMPDIR=/app/.convex-tmp; bun install --frozen-lockfile; \
              if [ -n \"$MACHINE_PROVISIONING_SECRET\" ]; then bunx convex env set MACHINE_PROVISIONING_SECRET \"$MACHINE_PROVISIONING_SECRET\"; fi; \
              if [ -n \"$MACHINE_TOKEN_TTL_MS\" ]; then bunx convex env set MACHINE_TOKEN_TTL_MS \"$MACHINE_TOKEN_TTL_MS\"; fi; \
              if [ -n \"$FLEET_SYNC_SECRET\" ]; then bunx convex env set FLEET_SYNC_SECRET \"$FLEET_SYNC_SECRET\"; fi; \
              bunx convex env list"

      - name: Prepare Convex deploy workspace
        run: |
          cd "$EFFECTIVE_APP_DIR"
          if [ -f .env ]; then
            echo "Renaming .env -> .env.bak (Convex self-hosted deploy)"
            mv -f .env .env.bak
          fi
          mkdir -p .convex-tmp

      - name: Deploy functions to Convex self-hosted
        env:
          CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br
          CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }}
        run: |
          docker run --rm -i \
            -v "$EFFECTIVE_APP_DIR":/app \
            -w /app \
            -e CI=true \
            -e CONVEX_SELF_HOSTED_URL \
            -e CONVEX_SELF_HOSTED_ADMIN_KEY \
            -e CONVEX_TMPDIR=/app/.convex-tmp \
            node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; export CONVEX_TMPDIR=/app/.convex-tmp; bun install --frozen-lockfile; bunx convex deploy"

      - name: Cleanup old convex build workdirs (keep last 2)
        run: |
          set -e
          ROOT="$HOME/apps"
          KEEP=2
          PATTERN='convex.build.*'
          LIST=$(find "$ROOT" -maxdepth 1 -type d -name "$PATTERN" | sort -r || true)
          echo "$LIST" | sed -n "1,${KEEP}p" | sed 's/^/Keeping: /' || true
          echo "$LIST" | sed "1,${KEEP}d" | while read dir; do
            [ -z "$dir" ] && continue
            echo "Removing $dir"
            chmod -R u+rwX "$dir" 2>/dev/null || true
            rm -rf "$dir" || {
              echo "Local rm failed, falling back to docker (root) cleanup for $dir..."
              docker run --rm -v "$dir":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true; rm -rf /target/* /target/.[!.]* /target/..?* 2>/dev/null || true' || true
              rm -rf "$dir" 2>/dev/null || rmdir "$dir" 2>/dev/null || true
            }
          done

  # NOTA: Job comentado porque nao ha runner Windows configurado.
  # Descomentar quando configurar um runner com labels: [self-hosted, windows, desktop]
  #
  # desktop_release:
  #   name: Desktop Release (Windows)
  #   timeout-minutes: 30
  #   if: ${{ startsWith(github.ref, 'refs/tags/v') }}
  #   runs-on: [ self-hosted, windows, desktop ]
  #   defaults:
  #     run:
  #       working-directory: apps/desktop
  #   steps:
  #     - name: Checkout
  #       uses: https://github.com/actions/checkout@v4
  #
  #     - name: Setup pnpm
  #       uses: https://github.com/pnpm/action-setup@v4
  #       with:
  #         version: 10.20.0
  #
  #     - name: Setup Node.js
  #       uses: https://github.com/actions/setup-node@v4
  #       with:
  #         node-version: 20
  #
  #     - name: Install deps (desktop)
  #       run: pnpm install --frozen-lockfile
  #
  #     - name: Build with Tauri
  #       uses: https://github.com/tauri-apps/tauri-action@v0
  #       env:
  #         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  #         TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
  #         TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
  #       with:
  #         projectPath: apps/desktop
  #
  #     - name: Upload bundles to VPS
  #       run: |
  #         # Upload via SCP (configurar chave SSH no runner Windows)
  #         # scp -r src-tauri/target/release/bundle/* user@vps:/var/www/updates/
  #         echo "TODO: Configurar upload para VPS"
