import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";

const mockFindFirst = vi.fn();
const mockFindMany = vi.fn();
const mockInsert = vi.fn();
const mockDelete = vi.fn();

vi.mock("@epicure/db", () => ({
  db: {
    query: {
      siteSettings: {
        findFirst: mockFindFirst,
        findMany: mockFindMany,
      },
    },
    insert: mockInsert,
    delete: mockDelete,
  },
  siteSettings: { key: "key", value: "value", isSecret: "is_secret", updatedAt: "updated_at", updatedById: "updated_by_id" },
  eq: vi.fn((a, b) => ({ a, b })),
}));

const insertChain = {
  values: vi.fn().mockReturnThis(),
  onConflictDoUpdate: vi.fn().mockResolvedValue(undefined),
};
const deleteChain = {
  where: vi.fn().mockResolvedValue(undefined),
};

const { getSiteSetting, setSiteSetting, isSecretKey } = await import("../site-settings");

beforeEach(() => {
  vi.clearAllMocks();
  mockInsert.mockReturnValue(insertChain);
  mockDelete.mockReturnValue(deleteChain);
});

afterEach(() => {
  delete process.env["OPENAI_API_KEY"];
  delete process.env["ANTHROPIC_API_KEY"];
});

describe("isSecretKey", () => {
  it("marks API keys as secret", () => {
    expect(isSecretKey("OPENAI_API_KEY")).toBe(true);
    expect(isSecretKey("ANTHROPIC_API_KEY")).toBe(true);
    expect(isSecretKey("VAPID_PRIVATE_KEY")).toBe(true);
  });

  it("marks non-secret settings as public", () => {
    expect(isSecretKey("OPENROUTER_DEFAULT_MODEL")).toBe(false);
    expect(isSecretKey("OLLAMA_BASE_URL")).toBe(false);
  });
});

describe("getSiteSetting", () => {
  it("returns DB value when present (plain)", async () => {
    mockFindFirst.mockResolvedValue({ value: "gpt-4o", isSecret: false });
    const val = await getSiteSetting("OPENROUTER_DEFAULT_MODEL");
    expect(val).toBe("gpt-4o");
  });

  it("decrypts DB value when secret", async () => {
    // Pre-encrypt something using the real encrypt function
    const { encrypt } = await import("../encrypt");
    const ciphertext = encrypt("sk-real-key");
    mockFindFirst.mockResolvedValue({ value: ciphertext, isSecret: true });

    const val = await getSiteSetting("OPENAI_API_KEY");
    expect(val).toBe("sk-real-key");
  });

  it("falls back to env var when no DB row", async () => {
    mockFindFirst.mockResolvedValue(null);
    process.env["OPENAI_API_KEY"] = "sk-from-env";
    const val = await getSiteSetting("OPENAI_API_KEY");
    expect(val).toBe("sk-from-env");
  });

  it("returns null when neither DB nor env is set", async () => {
    mockFindFirst.mockResolvedValue(null);
    const val = await getSiteSetting("OPENAI_API_KEY");
    expect(val).toBeNull();
  });

  it("falls back to env when DB throws", async () => {
    mockFindFirst.mockRejectedValue(new Error("DB error"));
    process.env["ANTHROPIC_API_KEY"] = "sk-anthropic-env";
    const val = await getSiteSetting("ANTHROPIC_API_KEY");
    expect(val).toBe("sk-anthropic-env");
  });
});

describe("setSiteSetting", () => {
  it("deletes the row when value is null", async () => {
    await setSiteSetting("OPENAI_API_KEY", null, "admin-user");
    expect(mockDelete).toHaveBeenCalled();
    expect(mockInsert).not.toHaveBeenCalled();
  });

  it("deletes the row when value is empty string", async () => {
    await setSiteSetting("OPENAI_API_KEY", "", "admin-user");
    expect(mockDelete).toHaveBeenCalled();
  });

  it("inserts encrypted value for secret key", async () => {
    await setSiteSetting("OPENAI_API_KEY", "sk-test", "admin-user");
    expect(mockInsert).toHaveBeenCalled();
    const inserted = insertChain.values.mock.calls[0]![0] as Record<string, unknown>;
    expect(inserted.isSecret).toBe(true);
    // stored value should not be plain text
    expect(inserted.value).not.toBe("sk-test");
    expect(typeof inserted.value).toBe("string");
  });

  it("inserts plain value for non-secret key", async () => {
    await setSiteSetting("OPENROUTER_DEFAULT_MODEL", "gpt-4o", "admin-user");
    const inserted = insertChain.values.mock.calls[0]![0] as Record<string, unknown>;
    expect(inserted.isSecret).toBe(false);
    expect(inserted.value).toBe("gpt-4o");
  });
});
