/**
 * Sync `.gluecron/workflows/*.yml` from a push into the `workflows` table,
 * then enqueue a run for every non-disabled workflow whose `on:` triggers
 * include `push`.
 *
 * This is the missing half of "push-triggered CI": nothing previously
 * wrote rows into `workflows` on push, so the table only ever got
 * populated by manual/test insertion, and `on: push` triggers never
 * actually fired automatically — only `workflow_dispatch` (manual/MCP),
 * PR slash commands, and `on: schedule` (autopilot tick) worked. Discovered
 * 2026-07-15 while auditing why a sibling platform's Gluecron migration
 * mirror never produced a running CI pipeline.
 *
 * Best-effort per file: a broken workflow YAML is recorded in `errors`
 * and skipped, never thrown — a bug here must not break the push itself.
 */

import { db } from "../db";
import { workflows } from "../db/schema";
import { getTree as realGetTree, getBlob as realGetBlob } from "../git/repository";
import { parseWorkflow } from "./workflow-parser";
import { enqueueRun as realEnqueueRun } from "./workflow-runner";

const WORKFLOWS_DIR = ".gluecron/workflows";

export interface PushWorkflowSyncResult {
  synced: number;
  enqueued: number;
  errors: string[];
}

export async function syncAndEnqueuePushWorkflows(
  opts: {
    owner: string;
    repo: string;
    repositoryId: string;
    branch: string;
    commitSha: string;
    triggeredBy?: string | null;
  },
  // Injectable for tests — avoids mock.module() on ../git/repository and
  // ./workflow-runner, both of which are imported by dozens of unrelated
  // test files and would leak a global mock across the whole test run.
  deps: {
    getTree: typeof realGetTree;
    getBlob: typeof realGetBlob;
    enqueueRun: typeof realEnqueueRun;
  } = { getTree: realGetTree, getBlob: realGetBlob, enqueueRun: realEnqueueRun }
): Promise<PushWorkflowSyncResult> {
  const result: PushWorkflowSyncResult = { synced: 0, enqueued: 0, errors: [] };

  // getTree never throws — it returns [] for a missing path/exec failure.
  const entries = await deps.getTree(opts.owner, opts.repo, opts.commitSha, WORKFLOWS_DIR);
  const ymlFiles = entries.filter(
    (e) => e.type === "blob" && /\.ya?ml$/i.test(e.name)
  );

  for (const file of ymlFiles) {
    const path = `${WORKFLOWS_DIR}/${file.name}`;
    try {
      const blob = await deps.getBlob(opts.owner, opts.repo, opts.commitSha, path);
      if (!blob || blob.isBinary) continue;

      const parseResult = parseWorkflow(blob.content);
      if (!parseResult.ok) {
        result.errors.push(`${file.name}: ${parseResult.error}`);
        continue;
      }
      const { workflow } = parseResult;

      const [row] = await db
        .insert(workflows)
        .values({
          repositoryId: opts.repositoryId,
          name: workflow.name || file.name,
          path,
          yaml: blob.content,
          parsed: JSON.stringify(workflow),
          onEvents: JSON.stringify(workflow.on),
        })
        .onConflictDoUpdate({
          target: [workflows.repositoryId, workflows.path],
          set: {
            name: workflow.name || file.name,
            yaml: blob.content,
            parsed: JSON.stringify(workflow),
            onEvents: JSON.stringify(workflow.on),
            updatedAt: new Date(),
          },
        })
        .returning({ id: workflows.id, disabled: workflows.disabled });

      if (!row) continue;
      result.synced += 1;

      if (!row.disabled && workflow.on.includes("push")) {
        try {
          await deps.enqueueRun({
            workflowId: row.id,
            repositoryId: opts.repositoryId,
            event: "push",
            ref: opts.branch,
            commitSha: opts.commitSha,
            triggeredBy: opts.triggeredBy ?? null,
          });
          result.enqueued += 1;
        } catch (err) {
          result.errors.push(
            `enqueue ${file.name}: ${err instanceof Error ? err.message : String(err)}`
          );
        }
      }
    } catch (err) {
      result.errors.push(
        `${file.name}: ${err instanceof Error ? err.message : String(err)}`
      );
    }
  }

  return result;
}
