import { describe, expect, it, vi } from "vitest";
import { createReadOnlyProxy } from "../lib/read-only-prisma.js";

function makeFakeClient() {
  const user = {
    findUnique: vi.fn(async () => ({ id: "u1" })),
    findMany: vi.fn(async () => []),
    create: vi.fn(async () => ({ id: "u1" })),
    update: vi.fn(async () => ({ id: "u1" })),
    upsert: vi.fn(async () => ({ id: "u1" })),
    delete: vi.fn(async () => ({ id: "u1" })),
    createMany: vi.fn(async () => ({ count: 1 })),
    createManyAndReturn: vi.fn(async () => [{ id: "u1" }]),
    updateMany: vi.fn(async () => ({ count: 1 })),
    deleteMany: vi.fn(async () => ({ count: 1 })),
  };
  const client = {
    user,
    $queryRaw: vi.fn(async () => [{ result: 1 }]),
    $queryRawUnsafe: vi.fn(async () => [{ result: 1 }]),
    $executeRaw: vi.fn(async () => 0),
    $executeRawUnsafe: vi.fn(async () => 0),
    $transaction: vi.fn(async () => []),
    $runCommandRaw: vi.fn(async () => ({ ok: 1 })),
  };
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return client as any;
}

describe("createReadOnlyProxy", () => {
  it("allows model reads", async () => {
    const proxy = createReadOnlyProxy(makeFakeClient());
    await expect(proxy.user.findUnique({ where: { id: "u1" } })).resolves.toEqual({ id: "u1" });
    await expect(proxy.user.findMany()).resolves.toEqual([]);
  });

  it("blocks model writes with clear error", () => {
    const proxy = createReadOnlyProxy(makeFakeClient());
    expect(() => proxy.user.create({ data: {} })).toThrow(
      /Write operation "create" on "user" not permitted/,
    );
    expect(() => proxy.user.update({ where: { id: "u1" }, data: {} })).toThrow(
      /Write operation "update"/,
    );
    expect(() => proxy.user.upsert({ where: { id: "u1" }, create: {}, update: {} })).toThrow(
      /Write operation "upsert"/,
    );
    expect(() => proxy.user.delete({ where: { id: "u1" } })).toThrow(/Write operation "delete"/);
    expect(() => proxy.user.createMany({ data: [] })).toThrow(/Write operation "createMany"/);
    expect(() => proxy.user.createManyAndReturn({ data: [] })).toThrow(
      /Write operation "createManyAndReturn"/,
    );
    expect(() => proxy.user.updateMany({ where: {}, data: {} })).toThrow(
      /Write operation "updateMany"/,
    );
    expect(() => proxy.user.deleteMany({ where: {} })).toThrow(/Write operation "deleteMany"/);
  });

  it("allows template-tagged $queryRaw (read-only by contract)", async () => {
    const proxy = createReadOnlyProxy(makeFakeClient());
    await expect(proxy.$queryRaw`SELECT 1`).resolves.toEqual([{ result: 1 }]);
  });

  it("blocks $queryRawUnsafe (DDL/DML smuggling)", () => {
    const proxy = createReadOnlyProxy(makeFakeClient());
    expect(() => proxy.$queryRawUnsafe("SELECT 1")).toThrow(
      /Raw\/escape operation "\$queryRawUnsafe" not permitted/,
    );
  });

  it("blocks $executeRaw and $executeRawUnsafe", () => {
    const proxy = createReadOnlyProxy(makeFakeClient());
    expect(() => proxy.$executeRaw`DELETE FROM users`).toThrow(
      /Raw\/escape operation "\$executeRaw" not permitted/,
    );
    expect(() => proxy.$executeRawUnsafe("DELETE FROM users")).toThrow(
      /Raw\/escape operation "\$executeRawUnsafe" not permitted/,
    );
  });

  it("blocks $transaction (interactive tx could contain writes)", () => {
    const proxy = createReadOnlyProxy(makeFakeClient());
    expect(() => proxy.$transaction([])).toThrow(
      /Raw\/escape operation "\$transaction" not permitted/,
    );
  });

  it("blocks $runCommandRaw (Mongo-style raw command)", () => {
    const proxy = createReadOnlyProxy(makeFakeClient());
    expect(() => proxy.$runCommandRaw({})).toThrow(
      /Raw\/escape operation "\$runCommandRaw" not permitted/,
    );
  });
});
