# deploy-lag-monitor.yml
#
# Runs every hour. Compares the HEAD SHA of origin/main against the SHA
# of the current Vercel production deployment. If they differ AND the
# main HEAD commit is more than 1 hour old, opens a GitHub issue (or
# comments on an existing open one) to alert about a stalled deploy.
#
# Required repository secrets (Settings -> Secrets and variables -> Actions):
#   VERCEL_TOKEN      - Vercel personal access token (vercel.com -> Account Settings -> Tokens)
#   VERCEL_PROJECT_ID - Vercel project ID (vercel.com -> Project Settings -> General, "Project ID" field)
#
# To test manually: Actions tab -> "Deploy Lag Monitor" -> "Run workflow"
# To silence a false alert: close the open "deploy-lag-alert" issue and
#   the next run will not re-open until a new lag condition is detected.

name: Deploy Lag Monitor

on:
  schedule:
    - cron: "0 * * * *"   # every hour at :00
  workflow_dispatch:        # manual trigger for testing

permissions:
  issues: write
  contents: read

jobs:
  check-deploy-lag:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Check deploy lag
        uses: actions/github-script@v7
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
          VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
        with:
          script: |
            const ALERT_LABEL = "deploy-lag-alert";
            const LAG_THRESHOLD_MS = 60 * 60 * 1000; // 1 hour

            // ── 1. Get HEAD SHA and commit timestamp from the checkout ──────
            const { execSync } = require("child_process");
            const mainSha = execSync("git rev-parse HEAD").toString().trim();
            const mainTimestampStr = execSync("git log -1 --format=%cI HEAD").toString().trim();
            const mainCommitMsg = execSync("git log -1 --format=%s HEAD").toString().trim();
            const mainTimestamp = new Date(mainTimestampStr);
            const commitAgeMs = Date.now() - mainTimestamp.getTime();

            core.info(`main HEAD: ${mainSha} (${mainTimestampStr})`);
            core.info(`Commit age: ${Math.round(commitAgeMs / 60000)} minutes`);

            // ── 2. Get current Vercel production deployment SHA ──────────────
            let vercelSha = null;
            let vercelDeployedAt = null;
            let vercelDeployUrl = null;

            if (!process.env.VERCEL_TOKEN || !process.env.VERCEL_PROJECT_ID) {
              core.warning("VERCEL_TOKEN or VERCEL_PROJECT_ID secret not set. Skipping check.");
              return;
            }

            try {
              const res = await fetch(
                `https://api.vercel.com/v6/deployments?projectId=${process.env.VERCEL_PROJECT_ID}&target=production&limit=1&state=READY`,
                { headers: { Authorization: `Bearer ${process.env.VERCEL_TOKEN}` } }
              );
              if (!res.ok) {
                core.warning(`Vercel API returned ${res.status}. Skipping check.`);
                return;
              }
              const data = await res.json();
              const latest = data.deployments?.[0];
              if (latest) {
                vercelSha = latest.meta?.githubCommitSha ?? null;
                vercelDeployedAt = latest.createdAt ? new Date(latest.createdAt).toISOString() : "unknown";
                vercelDeployUrl = latest.url ? `https://${latest.url}` : null;
              }
            } catch (err) {
              core.warning(`Failed to fetch Vercel deployment: ${err.message}. Skipping check.`);
              return;
            }

            core.info(`Vercel production SHA: ${vercelSha ?? "unknown"}`);

            // ── 3. Decide if there is a lag condition ───────────────────────
            // Only alert when:
            //   a) SHAs differ (or Vercel SHA unknown), AND
            //   b) The main commit is older than LAG_THRESHOLD_MS
            //      (prevents noise on commits that just landed)
            const shasMismatch = vercelSha !== mainSha;
            const isOldEnough = commitAgeMs > LAG_THRESHOLD_MS;

            if (!shasMismatch) {
              core.info("Production is up to date. No action needed.");
              return;
            }
            if (!isOldEnough) {
              const minutesOld = Math.round(commitAgeMs / 60000);
              core.info(`SHA mismatch but commit is only ${minutesOld}m old. Within grace period.`);
              return;
            }

            core.warning(`Deploy lag detected! main=${mainSha} vercel=${vercelSha ?? "unknown"}`);

            // ── 4. Check for an existing open alert issue ───────────────────
            const { data: existingIssues } = await github.rest.issues.listForRepo({
              owner: context.repo.owner,
              repo: context.repo.repo,
              labels: ALERT_LABEL,
              state: "open",
              per_page: 1,
            });

            const hoursOld = Math.round(commitAgeMs / 3_600_000);
            const body = [
              `**Production deploy lag detected.**`,
              ``,
              `| Field | Value |`,
              `|---|---|`,
              `| \`main\` HEAD SHA | \`${mainSha}\` |`,
              `| Vercel production SHA | \`${vercelSha ?? "unknown"}\` |`,
              `| Last commit | ${mainCommitMsg} |`,
              `| Commit pushed | ${mainTimestampStr} (~${hoursOld}h ago) |`,
              `| Vercel last deploy | ${vercelDeployedAt} |`,
              `${vercelDeployUrl ? `| Deploy URL | ${vercelDeployUrl} |` : ""}`,
              ``,
              `**Next steps:**`,
              `1. Open the Vercel dashboard and check for a failed or queued build.`,
              `2. Check GitHub Settings -> Webhooks -> Vercel webhook -> Recent Deliveries for failures.`,
              `3. If the webhook is healthy, trigger a manual redeploy from the Vercel dashboard.`,
              ``,
              `_Close this issue once the deploy is confirmed live._`,
            ].join("\n");

            if (existingIssues.length > 0) {
              // Comment on the existing issue instead of opening a duplicate.
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: existingIssues[0].number,
                body: `**Still lagging** (hourly check at ${new Date().toISOString()})\n\n${body}`,
              });
              core.info(`Commented on existing alert issue #${existingIssues[0].number}`);
            } else {
              // Ensure the label exists before creating the issue.
              try {
                await github.rest.issues.getLabel({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  name: ALERT_LABEL,
                });
              } catch {
                await github.rest.issues.createLabel({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  name: ALERT_LABEL,
                  color: "e11d48",
                  description: "Auto-opened by deploy-lag-monitor workflow",
                });
              }
              const { data: newIssue } = await github.rest.issues.create({
                owner: context.repo.owner,
                repo: context.repo.repo,
                title: "[ALERT] Production deploy lag detected",
                body,
                labels: [ALERT_LABEL],
              });
              core.warning(`Opened alert issue #${newIssue.number}: ${newIssue.html_url}`);
            }
