import { beforeEach, describe, expect, it, vi } from "vitest";
import type { TrpcContext } from "./_core/context";

const listStudentRecords = vi.fn();
const listStudentReportRows = vi.fn();
const getStudentBySeatNumber = vi.fn();
const getStudentRecordById = vi.fn();
const createStudentRecord = vi.fn();
const updateStudentRecord = vi.fn();
const deleteStudentRecord = vi.fn();
const createStudentRecordsBatch = vi.fn();
const findStudentRecordConflicts = vi.fn();

vi.mock("./db", async (importOriginal) => ({
  ...(await importOriginal<typeof import("./db")>()),
  listStudentRecords,
  listStudentReportRows,
  getStudentBySeatNumber,
  getStudentRecordById,
  createStudentRecord,
  updateStudentRecord,
  deleteStudentRecord,
  createStudentRecordsBatch,
  findStudentRecordConflicts,
}));

const { appRouter } = await import("./routers");

function contextForUser(id: number, role: "admin" | "editor" = "editor"): TrpcContext {
  const now = new Date();
  return {
    user: {
      id,
      openId: `test-user-${id}`,
      name: `User ${id}`,
      email: null,
      loginMethod: "test",
      role,
      isActive: true,
      createdAt: now,
      updatedAt: now,
      lastSignedIn: now,
    },
    req: { protocol: "https", headers: {} } as TrpcContext["req"],
    res: { clearCookie: () => undefined } as TrpcContext["res"],
  };
}

describe("owner-scoped student record procedures", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    listStudentRecords.mockResolvedValue({ rows: [], total: 0 });
    listStudentReportRows.mockResolvedValue([]);
    getStudentBySeatNumber.mockResolvedValue(null);
    getStudentRecordById.mockResolvedValue(null);
    createStudentRecord.mockResolvedValue(null);
    updateStudentRecord.mockResolvedValue(null);
    deleteStudentRecord.mockResolvedValue(false);
    createStudentRecordsBatch.mockResolvedValue(0);
    findStudentRecordConflicts.mockResolvedValue([]);
  });

  it("uses the administrator id and selected date range when generating their isolated report", async () => {
    const caller = appRouter.createCaller(contextForUser(41, "admin"));

    await caller.students.list({ page: 1, pageSize: 12 });
    await caller.students.report({ startDate: "2026-08-01", endDate: "2026-08-25" });

    expect(listStudentRecords).toHaveBeenCalledWith(41, 1, 12);
    expect(listStudentReportRows).toHaveBeenCalledWith(41, { startDate: "2026-08-01", endDate: "2026-08-25" });
  });

  it("allows an editor to manage their own records but rejects report access", async () => {
    const caller = appRouter.createCaller(contextForUser(41, "editor"));

    await caller.students.list({ page: 1, pageSize: 12 });
    await expect(caller.students.report({})).rejects.toMatchObject({ code: "FORBIDDEN" });

    expect(listStudentRecords).toHaveBeenCalledWith(41, 1, 12);
    expect(listStudentReportRows).not.toHaveBeenCalled();
  });

  it("rejects an inverted report date range before querying any report rows", async () => {
    const caller = appRouter.createCaller(contextForUser(41, "admin"));

    await expect(caller.students.report({ startDate: "2026-08-25", endDate: "2026-08-01" })).rejects.toMatchObject({ code: "BAD_REQUEST" });

    expect(listStudentReportRows).not.toHaveBeenCalled();
  });

  it("passes the owner id into direct lookups and mutations", async () => {
    const caller = appRouter.createCaller(contextForUser(77));
    const record = { seatNumber: "501", bankFormNumber: "801" };

    await caller.students.searchBySeatNumber({ seatNumber: "501" });
    await caller.students.getById({ id: 18 }).catch(() => undefined);
    await caller.students.create(record);
    await caller.students.update({ id: 18, record }).catch(() => undefined);
    await caller.students.remove({ id: 18 }).catch(() => undefined);

    expect(getStudentBySeatNumber).toHaveBeenCalledWith(77, "501");
    expect(getStudentRecordById).toHaveBeenCalledWith(77, 18);
    expect(createStudentRecord).toHaveBeenCalledWith(77, record);
    expect(updateStudentRecord).toHaveBeenCalledWith(77, 18, record);
    expect(deleteStudentRecord).toHaveBeenCalledWith(77, 18);
  });

  it("returns NOT_FOUND when another user attempts to read, update, or delete a record they do not own", async () => {
    const caller = appRouter.createCaller(contextForUser(77));
    const record = { seatNumber: "501", bankFormNumber: "801" };

    await expect(caller.students.getById({ id: 18 })).rejects.toMatchObject({ code: "NOT_FOUND" });
    await expect(caller.students.update({ id: 18, record })).rejects.toMatchObject({ code: "NOT_FOUND" });
    await expect(caller.students.remove({ id: 18 })).rejects.toMatchObject({ code: "NOT_FOUND" });

    expect(getStudentRecordById).toHaveBeenCalledWith(77, 18);
    expect(updateStudentRecord).toHaveBeenCalledWith(77, 18, record);
    expect(deleteStudentRecord).toHaveBeenCalledWith(77, 18);
  });

  it("maps a global unique conflict to a generic response without exposing the foreign owner", async () => {
    createStudentRecord.mockRejectedValueOnce({ code: "ER_DUP_ENTRY" });
    const caller = appRouter.createCaller(contextForUser(77));

    await expect(caller.students.create({ seatNumber: "501", bankFormNumber: "999" })).rejects.toMatchObject({
      code: "CONFLICT",
      message: "تعذر الحفظ لأن رقم الجلوس أو رقم الاستمارة البنكية مستخدم بالفعل.",
    });

    expect(createStudentRecord).toHaveBeenCalledWith(77, { seatNumber: "501", bankFormNumber: "999" });
  });
});
