/**
 * Post-import onboarding for a team migrating from GitHub.
 *
 * POST /import/bulk cloned repositories and stopped. A team that migrated an
 * org landed on bare repos — no branch protection, no labels, no gate
 * settings — and no evidence the migration was worth doing. The platform's
 * whole pitch (it finds problems in your code) was something they had to go
 * and discover for themselves, one repo at a time.
 *
 * runMigrationOnboarding bootstraps green defaults and scans the imported
 * code, returning a report the results page renders.
 *
 * The property that matters most here is FAULT ISOLATION: a migration that
 * imported 40 repositories must not be reported as a failure because the
 * scanner threw on one of them. These use injected failures to prove that.
 */

import { describe, expect, it, mock, afterEach } from "bun:test";
import { readFileSync } from "fs";

const SRC = readFileSync("src/lib/migration-onboarding.ts", "utf8");
const ROUTE = readFileSync("src/routes/import-bulk.tsx", "utf8");

describe("wiring", () => {
  it("import-bulk runs onboarding after a real import", () => {
    expect(ROUTE).toContain("runMigrationOnboarding");
  });

  it("only newly cloned repos are onboarded", () => {
    // "skipped-exists" repos were already on the platform and have settings
    // their owner chose; re-bootstrapping would fight those.
    expect(ROUTE).toContain('r.status === "success"');
    expect(ROUTE).not.toContain('r.status === "imported"');
  });

  it("a failure in onboarding cannot fail the completed migration", () => {
    // lastIndexOf: there is an earlier `return c.html(` for the dry-run
    // preview, which would slice to an empty string and pass vacuously.
    const block = ROUTE.slice(
      ROUTE.indexOf("const importedNames"),
      ROUTE.lastIndexOf("return c.html(")
    );
    expect(block.length).toBeGreaterThan(100);
    expect(block).toContain("try {");
    expect(block).toContain("catch");
    // The repositories are already cloned at this point; the results page
    // must still render.
    expect(block).not.toMatch(/throw\s/);
  });

  it("skips the welcome issue on imported repos", () => {
    // An imported repo arrives with real history and real issues. A
    // "welcome" issue on top of 400 migrated ones is noise.
    expect(SRC).toContain("skipWelcomeIssue: true");
  });

  it("bounds concurrency rather than scanning every repo at once", () => {
    // Each scan is a tree walk plus blob reads; an unbounded Promise.all over
    // a 100-repo org opens 100 concurrent walks against one disk and pool.
    expect(SRC).toContain("mapWithConcurrency");
    expect(SRC).toContain("DB_FANOUT_LIMIT");
  });
});

describe("fault isolation", () => {
  afterEach(() => {
    mock.restore();
  });

  it("one repo's scan failure does not sink the others", async () => {
    mock.module("../lib/repo-bootstrap", () => ({
      bootstrapRepository: async () => ({
        settingsCreated: true,
        protectionCreated: true,
        labelsCreated: 5,
      }),
    }));
    mock.module("../lib/gate", () => ({
      runSecretAndSecurityScan: async (_o: string, repo: string) => {
        if (repo === "explodes") throw new Error("scanner blew up");
        return {
          secretResult: { name: "Secrets", passed: true, details: "clean" },
          securityResult: { name: "Security", passed: true, details: "clean" },
          secrets: repo === "leaky" ? [{ a: 1 }, { b: 2 }] : [],
          securityIssues: [],
        };
      },
    }));

    const { runMigrationOnboarding } = await import("../lib/migration-onboarding");
    const report = await runMigrationOnboarding(
      [
        { id: "1", owner: "acme", name: "ok" },
        { id: "2", owner: "acme", name: "explodes" },
        { id: "3", owner: "acme", name: "leaky" },
      ],
      "user-1"
    );

    expect(report.totalRepos).toBe(3);
    // The thrower is reported, not thrown.
    const bad = report.repos.find((r) => r.name === "explodes")!;
    expect(bad.error).toContain("scanner blew up");
    // ...and it still got its green defaults.
    expect(bad.bootstrapped).toBe(true);
    // The others are unaffected and their findings are counted.
    expect(report.totalSecrets).toBe(2);
    expect(report.reposWithFindings).toBe(1);
    expect(report.reposBootstrapped).toBe(3);
  });

  it("a bootstrap failure still lets the scan run", async () => {
    mock.module("../lib/repo-bootstrap", () => ({
      bootstrapRepository: async () => {
        throw new Error("no protection for you");
      },
    }));
    mock.module("../lib/gate", () => ({
      runSecretAndSecurityScan: async () => ({
        secretResult: { name: "Secrets", passed: false, details: "1 secret" },
        securityResult: { name: "Security", passed: true, details: "clean" },
        secrets: [{ a: 1 }],
        securityIssues: [],
      }),
    }));

    const { runMigrationOnboarding } = await import("../lib/migration-onboarding");
    const report = await runMigrationOnboarding(
      [{ id: "1", owner: "acme", name: "repo" }],
      "user-1"
    );

    const r = report.repos[0];
    expect(r.bootstrapped).toBe(false);
    expect(r.error).toContain("bootstrap");
    // The scan is the value proposition — it must not be skipped because
    // the bootstrap step failed.
    expect(r.secretsFound).toBe(1);
    expect(report.totalSecrets).toBe(1);
  });

  it("returns an empty report for an empty import rather than throwing", async () => {
    const { runMigrationOnboarding } = await import("../lib/migration-onboarding");
    const report = await runMigrationOnboarding([], "user-1");
    expect(report.totalRepos).toBe(0);
    expect(report.totalSecrets).toBe(0);
    expect(report.reposWithFindings).toBe(0);
  });
});
