import { SettingsService, invalidEmails, parseEmailList } from "./settings.service";

/**
 * The db → env → default ladder is the whole contract of this service: it is
 * what lets the setting move out of the environment without changing how any
 * existing deployment behaves.
 */

function build(row: { value: string } | null, env?: string) {
  const prisma = {
    appSetting: {
      findUnique: jest.fn().mockResolvedValue(
        row ? { key: "k", updatedAt: new Date("2026-08-02"), updatedById: "u1", ...row } : null,
      ),
      upsert: jest.fn().mockResolvedValue({}),
    },
  };
  const config = { get: jest.fn().mockReturnValue(env) };
  return {
    service: new SettingsService(prisma as never, config as never),
    prisma,
  };
}

describe("notification admin emails resolve db > env > default", () => {
  it("prefers the stored row", async () => {
    const { service } = build({ value: "a@x.com,b@x.com" }, "env@x.com");

    await expect(service.notificationAdminEmails()).resolves.toMatchObject({
      value: ["a@x.com", "b@x.com"],
      source: "db",
      updatedById: "u1",
    });
  });

  it("falls back to the environment when nothing is stored", async () => {
    const { service } = build(null, "env@x.com, other@x.com");

    await expect(service.notificationAdminEmails()).resolves.toMatchObject({
      value: ["env@x.com", "other@x.com"],
      source: "env",
    });
  });

  it("falls back to the built-in defaults when neither is set", async () => {
    const { service } = build(null, undefined);

    const resolved = await service.notificationAdminEmails();
    expect(resolved.source).toBe("default");
    expect(resolved.value).toHaveLength(2);
  });

  it("treats a stored empty list as 'nobody', not as unset", async () => {
    // The regression this guards: falling through to env/defaults here would
    // keep mailing people who were deliberately removed.
    const { service } = build({ value: "" }, "env@x.com");

    await expect(service.notificationAdminEmails()).resolves.toMatchObject({
      value: [],
      source: "db",
    });
  });

  it("writes the list back as CSV", async () => {
    const { service, prisma } = build({ value: "" });

    await service.setNotificationAdminEmails(["a@x.com", "b@x.com"], "user-9");

    expect(prisma.appSetting.upsert).toHaveBeenCalledWith(
      expect.objectContaining({
        create: expect.objectContaining({ value: "a@x.com,b@x.com", updatedById: "user-9" }),
        update: expect.objectContaining({ value: "a@x.com,b@x.com", updatedById: "user-9" }),
      }),
    );
  });
});

describe("email list parsing", () => {
  it("trims and drops blanks", () => {
    expect(parseEmailList(" a@x.com , ,b@x.com ")).toEqual(["a@x.com", "b@x.com"]);
  });

  it("rejects entries that are not addresses at all", () => {
    expect(invalidEmails(["ok@x.com", "nope", "also@bad"])).toEqual([
      "nope",
      "also@bad",
    ]);
  });
});
