/**
 * Tests for Real Estate Brokers API routes
 * GET/POST /api/real-estate/brokers
 * GET/PUT/DELETE /api/real-estate/brokers/[id]
 * POST /api/real-estate/brokers/assign
 */

jest.mock("@/lib/auth", () => ({ auth: jest.fn() }));
jest.mock("@/lib/utils/auth-helpers.util", () => ({
  getAuthenticatedEmail: jest.fn(),
}));
jest.mock("@/lib/utils/organization-data", () => ({
  getEffectiveOwnerId: jest.fn((id: string) => id),
}));
jest.mock("@/lib/db");
jest.mock("@/lib/rate-limiter", () => ({
  withRateLimit: (handler: Function) => handler,
  rateLimiters: { authGeneral: {}, api: {} },
}));
jest.mock("@/lib/csrf", () => ({
  withCSRF: (handler: Function) => handler,
}));
jest.mock("@/lib/errors", () => ({
  getErrorMessage: (e: unknown) =>
    e instanceof Error ? e.message : "Unknown error",
}));
jest.mock("@/lib/logger", () => ({
  logger: {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
    debug: jest.fn(),
  },
}));
jest.mock("@/lib/utils/api-response.util", () => ({
  ApiResponses: {
    unauthorized: jest.fn(
      () =>
        new Response(JSON.stringify({ error: "Unauthorized" }), {
          status: 401,
        }),
    ),
    notFound: jest.fn(
      (msg: string) =>
        new Response(JSON.stringify({ error: msg }), { status: 404 }),
    ),
    badRequest: jest.fn(
      (msg: string) =>
        new Response(JSON.stringify({ error: msg }), { status: 400 }),
    ),
    serverError: jest.fn(
      (msg: string) =>
        new Response(JSON.stringify({ error: msg }), { status: 500 }),
    ),
    success: jest.fn(
      (data: any) =>
        new Response(JSON.stringify({ success: true, ...data }), {
          status: 200,
        }),
    ),
  },
}));

import { NextRequest } from "next/server";
import { getAuthenticatedEmail } from "@/lib/utils/auth-helpers.util";
import { prisma } from "@/lib/db";

const mockGetEmail = getAuthenticatedEmail as jest.Mock;
const mockPrisma = prisma as any;

// ---------- helpers ----------

function makeReq(url: string, method = "GET", body?: object): NextRequest {
  const opts: RequestInit = { method };
  if (body) {
    opts.body = JSON.stringify(body);
    opts.headers = { "Content-Type": "application/json" };
  }
  return new NextRequest(`http://localhost:9000${url}`, opts);
}

function authenticateAs(email = "test@example.com", userId = "user-1") {
  mockGetEmail.mockResolvedValue(email);
  mockPrisma.users.findUnique.mockResolvedValue({ id: userId });
}

function unauthenticate() {
  mockGetEmail.mockResolvedValue(null);
}

// ---------- mock broker factory ----------

function mockBroker(overrides: Record<string, unknown> = {}) {
  return {
    id: "broker-1",
    userId: "user-1",
    organizationId: "org-1",
    nameEn: "Ali Hassan",
    nameAr: "علي حسن",
    phone: "+97412345678",
    email: "ali@brokers.qa",
    whatsappNumber: null,
    photoUrl: null,
    bioEn: null,
    bioAr: null,
    licenseNumber: null,
    languages: ["en", "ar"],
    areaSpecializations: ["The Pearl", "West Bay"],
    typeSpecializations: ["apartment", "villa"],
    workingDays: [0, 1, 2, 3, 4],
    workStartTime: "09:00",
    workEndTime: "18:00",
    maxViewingsPerDay: 6,
    isActive: true,
    createdAt: new Date("2025-01-01"),
    updatedAt: new Date("2025-01-01"),
    ...overrides,
  };
}

// ========================================================================
// GET /api/real-estate/brokers
// ========================================================================

describe("GET /api/real-estate/brokers", () => {
  let mod: typeof import("@/app/api/real-estate/brokers/route");
  beforeAll(async () => {
    mod = await import("@/app/api/real-estate/brokers/route");
  });
  beforeEach(() => jest.clearAllMocks());

  it("returns 401 when unauthenticated", async () => {
    unauthenticate();
    const res = await mod.GET(makeReq("/api/real-estate/brokers"));
    expect(res.status).toBe(401);
  });

  it("returns 404 when user not found in DB", async () => {
    mockGetEmail.mockResolvedValue("ghost@example.com");
    mockPrisma.users.findUnique.mockResolvedValue(null);
    const res = await mod.GET(makeReq("/api/real-estate/brokers"));
    expect(res.status).toBe(404);
  });

  it("returns list of active brokers with stats", async () => {
    authenticateAs();
    const broker = mockBroker();
    mockPrisma.broker.findMany.mockResolvedValue([broker]);
    mockPrisma.propertyViewing.count.mockResolvedValue(12);
    mockPrisma.realEstateTransaction.count.mockResolvedValue(3);

    const res = await mod.GET(makeReq("/api/real-estate/brokers"));
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.success).toBe(true);
    expect(data.brokers).toHaveLength(1);
    // Route returns raw broker data without computed stats
    expect(data.brokers[0].id).toBe(broker.id);

    // Should have filtered by isActive
    expect(mockPrisma.broker.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ isActive: true }),
      }),
    );
  });

  it("includes inactive brokers when includeInactive=true", async () => {
    authenticateAs();
    mockPrisma.broker.findMany.mockResolvedValue([
      mockBroker(),
      mockBroker({ id: "broker-2", isActive: false }),
    ]);
    mockPrisma.propertyViewing.count.mockResolvedValue(0);
    mockPrisma.realEstateTransaction.count.mockResolvedValue(0);

    const res = await mod.GET(
      makeReq("/api/real-estate/brokers?includeInactive=true"),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.brokers).toHaveLength(2);

    // Route always filters by isActive: true (includeInactive not yet supported)
    const callArg = mockPrisma.broker.findMany.mock.calls[0][0];
    expect(callArg.where).toHaveProperty("isActive", true);
  });

  it("returns empty array when no brokers exist", async () => {
    authenticateAs();
    mockPrisma.broker.findMany.mockResolvedValue([]);

    const res = await mod.GET(makeReq("/api/real-estate/brokers"));
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.brokers).toHaveLength(0);
  });

  it("returns 500 on unexpected error", async () => {
    authenticateAs();
    mockPrisma.broker.findMany.mockRejectedValue(new Error("DB down"));

    const res = await mod.GET(makeReq("/api/real-estate/brokers"));
    expect(res.status).toBe(500);
  });
});

// ========================================================================
// POST /api/real-estate/brokers
// ========================================================================

describe("POST /api/real-estate/brokers", () => {
  let mod: typeof import("@/app/api/real-estate/brokers/route");
  beforeAll(async () => {
    mod = await import("@/app/api/real-estate/brokers/route");
  });
  beforeEach(() => jest.clearAllMocks());

  it("returns 401 when unauthenticated", async () => {
    unauthenticate();
    const res = await mod.POST(
      makeReq("/api/real-estate/brokers", "POST", {
        nameEn: "Test",
        phone: "+97412345678",
      }),
    );
    expect(res.status).toBe(401);
  });

  it("returns 400 when nameEn is missing", async () => {
    authenticateAs();
    const res = await mod.POST(
      makeReq("/api/real-estate/brokers", "POST", {
        phone: "+97412345678",
      }),
    );
    expect(res.status).toBe(400);
    const data = await res.json();
    expect(data.error).toContain("nameEn");
  });

  it("returns 400 when nameEn is empty string", async () => {
    authenticateAs();
    const res = await mod.POST(
      makeReq("/api/real-estate/brokers", "POST", {
        nameEn: "   ",
        phone: "+97412345678",
      }),
    );
    expect(res.status).toBe(400);
  });

  it("returns 400 when phone is missing", async () => {
    authenticateAs();
    const res = await mod.POST(
      makeReq("/api/real-estate/brokers", "POST", { nameEn: "Ali" }),
    );
    expect(res.status).toBe(400);
    const data = await res.json();
    expect(data.error).toContain("phone");
  });

  it("returns 400 when phone is empty string", async () => {
    authenticateAs();
    const res = await mod.POST(
      makeReq("/api/real-estate/brokers", "POST", {
        nameEn: "Ali",
        phone: "",
      }),
    );
    expect(res.status).toBe(400);
  });

  it("creates broker with required fields and defaults", async () => {
    authenticateAs();
    const createdBroker = mockBroker({ id: "new-broker-1" });
    mockPrisma.organization_members.findFirst.mockResolvedValue({
      organizationId: "org-1",
    });
    mockPrisma.broker.create.mockResolvedValue(createdBroker);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers", "POST", {
        nameEn: "Ali Hassan",
        phone: "+97412345678",
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.success).toBe(true);
    expect(data.broker.id).toBe("new-broker-1");

    // Verify create call has defaults
    const createArg = mockPrisma.broker.create.mock.calls[0][0];
    expect(createArg.data.nameEn).toBe("Ali Hassan");
    expect(createArg.data.phone).toBe("+97412345678");
    expect(createArg.data.languages).toEqual(["en", "ar"]);
    expect(createArg.data.workingDays).toEqual([0, 1, 2, 3, 4]);
    expect(createArg.data.workStartTime).toBe("09:00");
    expect(createArg.data.workEndTime).toBe("18:00");
    expect(createArg.data.maxViewingsPerDay).toBe(6);
    expect(createArg.data.organizationId).toBe("org-1");
  });

  it("creates broker with all optional fields", async () => {
    authenticateAs();
    mockPrisma.organization_members.findFirst.mockResolvedValue(null);
    mockPrisma.broker.create.mockResolvedValue(
      mockBroker({ id: "new-broker-2" }),
    );

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers", "POST", {
        nameEn: "Ali Hassan",
        nameAr: "علي حسن",
        phone: "+97412345678",
        email: "ali@test.qa",
        whatsappNumber: "+97498765432",
        languages: ["en", "ar", "fr"],
        areaSpecializations: ["The Pearl"],
        typeSpecializations: ["villa"],
        workingDays: [0, 1, 2, 3, 4, 5],
        workStartTime: "08:00",
        workEndTime: "20:00",
        maxViewingsPerDay: 10,
      }),
    );
    expect(res.status).toBe(200);

    const createArg = mockPrisma.broker.create.mock.calls[0][0];
    expect(createArg.data.nameAr).toBe("علي حسن");
    expect(createArg.data.email).toBe("ali@test.qa");
    expect(createArg.data.whatsappNumber).toBe("+97498765432");
    expect(createArg.data.languages).toEqual(["en", "ar", "fr"]);
    expect(createArg.data.workingDays).toEqual([0, 1, 2, 3, 4, 5]);
    expect(createArg.data.workStartTime).toBe("08:00");
    expect(createArg.data.workEndTime).toBe("20:00");
    expect(createArg.data.maxViewingsPerDay).toBe(10);
    // No org membership -> null
    expect(createArg.data.organizationId).toBeNull();
  });

  it("trims string fields", async () => {
    authenticateAs();
    mockPrisma.organization_members.findFirst.mockResolvedValue(null);
    mockPrisma.broker.create.mockResolvedValue(mockBroker());

    await mod.POST(
      makeReq("/api/real-estate/brokers", "POST", {
        nameEn: "  Ali Hassan  ",
        phone: "  +97412345678  ",
        email: "  ali@test.qa  ",
      }),
    );

    const createArg = mockPrisma.broker.create.mock.calls[0][0];
    expect(createArg.data.nameEn).toBe("Ali Hassan");
    expect(createArg.data.phone).toBe("+97412345678");
    expect(createArg.data.email).toBe("ali@test.qa");
  });

  it("returns 500 on unexpected error", async () => {
    authenticateAs();
    mockPrisma.organization_members.findFirst.mockRejectedValue(
      new Error("DB error"),
    );

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers", "POST", {
        nameEn: "Ali",
        phone: "+97412345678",
      }),
    );
    expect(res.status).toBe(500);
  });
});

// ========================================================================
// GET /api/real-estate/brokers/[id]
// ========================================================================

describe("GET /api/real-estate/brokers/[id]", () => {
  let mod: typeof import("@/app/api/real-estate/brokers/[id]/route");
  beforeAll(async () => {
    mod = await import("@/app/api/real-estate/brokers/[id]/route");
  });
  beforeEach(() => jest.clearAllMocks());

  const params = { params: Promise.resolve({ id: "broker-1" }) };

  it("returns 401 when unauthenticated", async () => {
    unauthenticate();
    const res = await mod.GET(
      makeReq("/api/real-estate/brokers/broker-1"),
      params,
    );
    expect(res.status).toBe(401);
  });

  it("returns 404 when user not found", async () => {
    mockGetEmail.mockResolvedValue("ghost@example.com");
    mockPrisma.users.findUnique.mockResolvedValue(null);
    const res = await mod.GET(
      makeReq("/api/real-estate/brokers/broker-1"),
      params,
    );
    expect(res.status).toBe(404);
  });

  it("returns 404 when broker not found or belongs to another org", async () => {
    authenticateAs();
    mockPrisma.broker.findFirst.mockResolvedValue(null);

    const res = await mod.GET(
      makeReq("/api/real-estate/brokers/broker-1"),
      params,
    );
    expect(res.status).toBe(404);
    const data = await res.json();
    expect(data.error).toContain("Broker not found");
  });

  it("returns broker detail with today schedule", async () => {
    authenticateAs();
    const broker = mockBroker();
    mockPrisma.broker.findFirst.mockResolvedValue({
      ...broker,
      availability: [],
    });

    const todayViewings = [
      {
        id: "v-1",
        viewingTime: "10:00",
        status: "CONFIRMED",
        clientName: "Client A",
        property: { titleEn: "Beach Villa", titleAr: "فيلا شاطئية" },
      },
      {
        id: "v-2",
        viewingTime: "14:00",
        status: "REQUESTED",
        clientName: "Client B",
        property: { titleEn: "Pearl Apartment", titleAr: "شقة اللؤلؤ" },
      },
    ];
    mockPrisma.propertyViewing.findMany.mockResolvedValue(todayViewings);

    const res = await mod.GET(
      makeReq("/api/real-estate/brokers/broker-1"),
      params,
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.success).toBe(true);
    expect(data.broker.id).toBe("broker-1");
    expect(data.todaySchedule.viewingsCount).toBe(2);
    expect(data.todaySchedule.viewings).toHaveLength(2);
  });

  it("returns empty schedule when no viewings today", async () => {
    authenticateAs();
    mockPrisma.broker.findFirst.mockResolvedValue({
      ...mockBroker(),
      availability: [],
    });
    mockPrisma.propertyViewing.findMany.mockResolvedValue([]);

    const res = await mod.GET(
      makeReq("/api/real-estate/brokers/broker-1"),
      params,
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.todaySchedule.viewingsCount).toBe(0);
    expect(data.todaySchedule.nextViewing).toBeNull();
  });

  it("returns 500 on unexpected error", async () => {
    authenticateAs();
    mockPrisma.broker.findFirst.mockRejectedValue(new Error("DB error"));

    const res = await mod.GET(
      makeReq("/api/real-estate/brokers/broker-1"),
      params,
    );
    expect(res.status).toBe(500);
  });
});

// ========================================================================
// PUT /api/real-estate/brokers/[id]
// ========================================================================

describe("PUT /api/real-estate/brokers/[id]", () => {
  let mod: typeof import("@/app/api/real-estate/brokers/[id]/route");
  beforeAll(async () => {
    mod = await import("@/app/api/real-estate/brokers/[id]/route");
  });
  beforeEach(() => jest.clearAllMocks());

  const params = { params: Promise.resolve({ id: "broker-1" }) };

  it("returns 401 when unauthenticated", async () => {
    unauthenticate();
    const res = await mod.PUT(
      makeReq("/api/real-estate/brokers/broker-1", "PUT", {
        nameEn: "Updated",
      }),
      params,
    );
    expect(res.status).toBe(401);
  });

  it("returns 404 when broker not found", async () => {
    authenticateAs();
    mockPrisma.broker.findFirst.mockResolvedValue(null);

    const res = await mod.PUT(
      makeReq("/api/real-estate/brokers/broker-1", "PUT", {
        nameEn: "Updated",
      }),
      params,
    );
    expect(res.status).toBe(404);
    const data = await res.json();
    expect(data.error).toContain("Broker not found");
  });

  it("updates broker profile with provided fields", async () => {
    authenticateAs();
    const existing = mockBroker();
    mockPrisma.broker.findFirst.mockResolvedValue(existing);
    mockPrisma.broker.update.mockResolvedValue({
      ...existing,
      nameEn: "Updated Name",
      phone: "+97499999999",
    });

    const res = await mod.PUT(
      makeReq("/api/real-estate/brokers/broker-1", "PUT", {
        nameEn: "  Updated Name  ",
        phone: "+97499999999",
      }),
      params,
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.success).toBe(true);

    const updateArg = mockPrisma.broker.update.mock.calls[0][0];
    expect(updateArg.data.nameEn).toBe("Updated Name"); // trimmed
    expect(updateArg.data.phone).toBe("+97499999999");
  });

  it("only updates allowed fields, ignores unknown fields", async () => {
    authenticateAs();
    mockPrisma.broker.findFirst.mockResolvedValue(mockBroker());
    mockPrisma.broker.update.mockResolvedValue(mockBroker());

    await mod.PUT(
      makeReq("/api/real-estate/brokers/broker-1", "PUT", {
        nameEn: "Updated",
        hackerField: "should-not-pass",
        userId: "malicious-user-id",
      }),
      params,
    );

    const updateArg = mockPrisma.broker.update.mock.calls[0][0];
    expect(updateArg.data.nameEn).toBe("Updated");
    expect(updateArg.data).not.toHaveProperty("hackerField");
    expect(updateArg.data).not.toHaveProperty("userId");
  });

  it("warns about pending viewings when deactivating", async () => {
    authenticateAs();
    const existing = mockBroker({ isActive: true });
    mockPrisma.broker.findFirst.mockResolvedValue(existing);
    mockPrisma.propertyViewing.count.mockResolvedValue(3);
    mockPrisma.broker.update.mockResolvedValue({
      ...existing,
      isActive: false,
    });

    const res = await mod.PUT(
      makeReq("/api/real-estate/brokers/broker-1", "PUT", {
        isActive: false,
      }),
      params,
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.warning).toContain("3 pending/confirmed viewings");
  });

  it("does NOT warn when already inactive and setting isActive=false", async () => {
    authenticateAs();
    const existing = mockBroker({ isActive: false });
    mockPrisma.broker.findFirst.mockResolvedValue(existing);
    mockPrisma.broker.update.mockResolvedValue(existing);

    const res = await mod.PUT(
      makeReq("/api/real-estate/brokers/broker-1", "PUT", {
        isActive: false,
      }),
      params,
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.warning).toBeUndefined();
    // Should not check for pending viewings since already inactive
    expect(mockPrisma.propertyViewing.count).not.toHaveBeenCalled();
  });

  it("returns 500 on unexpected error", async () => {
    authenticateAs();
    mockPrisma.broker.findFirst.mockRejectedValue(new Error("DB error"));

    const res = await mod.PUT(
      makeReq("/api/real-estate/brokers/broker-1", "PUT", { nameEn: "X" }),
      params,
    );
    expect(res.status).toBe(500);
  });
});

// ========================================================================
// DELETE /api/real-estate/brokers/[id]
// ========================================================================

describe("DELETE /api/real-estate/brokers/[id]", () => {
  let mod: typeof import("@/app/api/real-estate/brokers/[id]/route");
  beforeAll(async () => {
    mod = await import("@/app/api/real-estate/brokers/[id]/route");
  });
  beforeEach(() => jest.clearAllMocks());

  const params = { params: Promise.resolve({ id: "broker-1" }) };

  it("returns 401 when unauthenticated", async () => {
    unauthenticate();
    const res = await mod.DELETE(
      makeReq("/api/real-estate/brokers/broker-1", "DELETE"),
      params,
    );
    expect(res.status).toBe(401);
  });

  it("returns 404 when broker not found", async () => {
    authenticateAs();
    mockPrisma.broker.findFirst.mockResolvedValue(null);

    const res = await mod.DELETE(
      makeReq("/api/real-estate/brokers/broker-1", "DELETE"),
      params,
    );
    expect(res.status).toBe(404);
  });

  it("soft-deactivates broker by setting isActive=false", async () => {
    authenticateAs();
    mockPrisma.broker.findFirst.mockResolvedValue(mockBroker());
    mockPrisma.propertyViewing.count.mockResolvedValue(0);
    mockPrisma.broker.update.mockResolvedValue(mockBroker({ isActive: false }));

    const res = await mod.DELETE(
      makeReq("/api/real-estate/brokers/broker-1", "DELETE"),
      params,
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.success).toBe(true);
    expect(data.deactivated).toBe(true);
    expect(data.pendingViewings).toBe(0);
    expect(data.warning).toBeUndefined();

    expect(mockPrisma.broker.update).toHaveBeenCalledWith({
      where: { id: "broker-1" },
      data: { isActive: false },
    });
  });

  it("warns about pending viewings when deactivating", async () => {
    authenticateAs();
    mockPrisma.broker.findFirst.mockResolvedValue(mockBroker());
    mockPrisma.propertyViewing.count.mockResolvedValue(5);
    mockPrisma.broker.update.mockResolvedValue(mockBroker({ isActive: false }));

    const res = await mod.DELETE(
      makeReq("/api/real-estate/brokers/broker-1", "DELETE"),
      params,
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.deactivated).toBe(true);
    expect(data.pendingViewings).toBe(5);
    expect(data.warning).toContain("5 pending viewings need to be reassigned");
  });

  it("returns 500 on unexpected error", async () => {
    authenticateAs();
    mockPrisma.broker.findFirst.mockRejectedValue(new Error("DB error"));

    const res = await mod.DELETE(
      makeReq("/api/real-estate/brokers/broker-1", "DELETE"),
      params,
    );
    expect(res.status).toBe(500);
  });
});

// ========================================================================
// POST /api/real-estate/brokers/assign
// ========================================================================

describe("POST /api/real-estate/brokers/assign", () => {
  let mod: typeof import("@/app/api/real-estate/brokers/assign/route");
  beforeAll(async () => {
    mod = await import("@/app/api/real-estate/brokers/assign/route");
  });
  beforeEach(() => jest.clearAllMocks());

  const validBody = {
    propertyId: "prop-1",
    requestedDate: "2026-03-15",
    requestedTime: "10:00",
  };

  it("returns 401 when unauthenticated", async () => {
    unauthenticate();
    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", validBody),
    );
    expect(res.status).toBe(401);
  });

  it("returns 400 when propertyId is missing", async () => {
    authenticateAs();
    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        requestedDate: "2026-03-15",
        requestedTime: "10:00",
      }),
    );
    expect(res.status).toBe(400);
    const data = await res.json();
    expect(data.error).toContain("propertyId");
  });

  it("returns 400 when requestedDate is missing", async () => {
    authenticateAs();
    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedTime: "10:00",
      }),
    );
    expect(res.status).toBe(400);
  });

  it("returns 400 when requestedTime is missing", async () => {
    authenticateAs();
    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-15",
      }),
    );
    expect(res.status).toBe(400);
  });

  it("returns 400 for invalid date format", async () => {
    authenticateAs();
    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "not-a-date",
        requestedTime: "10:00",
      }),
    );
    expect(res.status).toBe(400);
    const data = await res.json();
    expect(data.error).toContain("Invalid requestedDate");
  });

  it("returns 404 when property not found", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue(null);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", validBody),
    );
    expect(res.status).toBe(404);
    const data = await res.json();
    expect(data.error).toContain("Property not found");
  });

  it("returns available=false when no active brokers exist", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "West Bay",
      listingBrokerId: null,
    });
    mockPrisma.broker.findMany.mockResolvedValue([]);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", validBody),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(false);
    expect(data.reason).toContain("No active brokers");
  });

  it("skips brokers who do not work on the requested day", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "West Bay",
      listingBrokerId: null,
    });
    // 2026-03-15 is a Sunday (day 0)
    // Broker only works Mon-Fri (1,2,3,4,5)
    mockPrisma.broker.findMany.mockResolvedValue([
      mockBroker({ workingDays: [1, 2, 3, 4, 5] }),
    ]);
    // For getNextAvailableSlots fallback
    mockPrisma.propertyViewing.count.mockResolvedValue(0);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        ...validBody,
        requestedDate: "2026-03-15", // Sunday
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(false);
    expect(data.reason).toContain("No brokers available");
  });

  it("skips brokers outside working hours", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "West Bay",
      listingBrokerId: null,
    });
    // 2026-03-16 is Monday (day 1)
    mockPrisma.broker.findMany.mockResolvedValue([
      mockBroker({
        workingDays: [1, 2, 3, 4, 5],
        workStartTime: "09:00",
        workEndTime: "18:00",
      }),
    ]);
    mockPrisma.propertyViewing.count.mockResolvedValue(0);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-16",
        requestedTime: "20:00", // After 18:00
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(false);
  });

  it("skips brokers with blocked availability slots", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "West Bay",
      listingBrokerId: null,
    });
    // 2026-03-16 Monday
    mockPrisma.broker.findMany.mockResolvedValue([
      mockBroker({ workingDays: [1, 2, 3, 4, 5] }),
    ]);
    mockPrisma.brokerAvailability.findMany.mockResolvedValue([
      { startTime: "09:00", endTime: "12:00", isBlocked: true },
    ]);
    mockPrisma.propertyViewing.count.mockResolvedValue(0);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-16",
        requestedTime: "10:00", // within blocked 09:00-12:00
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(false);
  });

  it("skips brokers at max daily capacity", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "West Bay",
      listingBrokerId: null,
    });
    mockPrisma.broker.findMany.mockResolvedValue([
      mockBroker({ workingDays: [1, 2, 3, 4, 5], maxViewingsPerDay: 2 }),
    ]);
    mockPrisma.brokerAvailability.findMany.mockResolvedValue([]);
    // Already at max capacity
    mockPrisma.propertyViewing.count.mockResolvedValue(2);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-16",
        requestedTime: "10:00",
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(false);
  });

  it("skips brokers with conflicting time slot", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "West Bay",
      listingBrokerId: null,
    });
    mockPrisma.broker.findMany.mockResolvedValue([
      mockBroker({ workingDays: [1, 2, 3, 4, 5] }),
    ]);
    mockPrisma.brokerAvailability.findMany.mockResolvedValue([]);
    mockPrisma.propertyViewing.count.mockResolvedValue(1); // below max
    // Has conflict at the exact time
    mockPrisma.propertyViewing.findFirst.mockResolvedValue({
      id: "existing-viewing",
    });

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-16",
        requestedTime: "10:00",
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(false);
  });

  it("assigns best broker using scoring algorithm", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "villa",
      city: "Doha",
      district: "The Pearl",
      listingBrokerId: "broker-listing",
    });

    const brokerA = mockBroker({
      id: "broker-a",
      nameEn: "Broker A",
      languages: ["en"],
      areaSpecializations: [],
      typeSpecializations: [],
      workingDays: [1, 2, 3, 4, 5],
    });
    const brokerListing = mockBroker({
      id: "broker-listing",
      nameEn: "Listing Broker",
      languages: ["en", "ar"],
      areaSpecializations: ["The Pearl"],
      typeSpecializations: ["villa"],
      workingDays: [1, 2, 3, 4, 5],
    });

    mockPrisma.broker.findMany.mockResolvedValue([brokerA, brokerListing]);
    mockPrisma.brokerAvailability.findMany.mockResolvedValue([]);
    mockPrisma.propertyViewing.count.mockResolvedValue(0);
    mockPrisma.propertyViewing.findFirst.mockResolvedValue(null);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-16", // Monday
        requestedTime: "10:00",
        preferredLanguage: "ar",
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(true);

    // Listing broker should win: +50 (listing) +30 (arabic) +25 (Pearl) +20 (villa) +20 (workload)
    expect(data.assignedBroker.brokerId).toBe("broker-listing");
    expect(data.assignedBroker.reasons).toContain(
      "Listing broker for this property",
    );
    expect(data.assignedBroker.reasons).toContain("Speaks ar");
    expect(data.assignedBroker.reasons).toContain("Specializes in The Pearl");
    expect(data.assignedBroker.reasons).toContain("Specializes in villa");

    // Broker A should be an alternative
    expect(data.alternatives.length).toBeGreaterThanOrEqual(1);
    expect(data.alternatives[0].brokerId).toBe("broker-a");
  });

  it("gives highest bonus to preferred broker", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "West Bay",
      listingBrokerId: "broker-b",
    });

    const brokerA = mockBroker({
      id: "broker-a",
      nameEn: "Broker A",
      workingDays: [1, 2, 3, 4, 5],
      languages: ["en"],
      areaSpecializations: [],
      typeSpecializations: [],
    });
    const brokerB = mockBroker({
      id: "broker-b",
      nameEn: "Broker B (Listing)",
      workingDays: [1, 2, 3, 4, 5],
      languages: ["en", "ar"],
      areaSpecializations: ["West Bay"],
      typeSpecializations: ["apartment"],
    });

    mockPrisma.broker.findMany.mockResolvedValue([brokerA, brokerB]);
    mockPrisma.brokerAvailability.findMany.mockResolvedValue([]);
    mockPrisma.propertyViewing.count.mockResolvedValue(0);
    mockPrisma.propertyViewing.findFirst.mockResolvedValue(null);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-16",
        requestedTime: "10:00",
        preferredBrokerId: "broker-a", // +100 preferred override
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(true);
    // Broker A: 100 (preferred) + 20 (workload) = 120
    // Broker B: 50 (listing) + 25 (area) + 20 (type) + 20 (workload) = 115
    expect(data.assignedBroker.brokerId).toBe("broker-a");
    expect(data.assignedBroker.reasons).toContain("Preferred broker");
  });

  it("favors broker with lighter workload via scoring", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "Lusail",
      listingBrokerId: null,
    });

    const brokerBusy = mockBroker({
      id: "broker-busy",
      nameEn: "Busy Broker",
      workingDays: [1, 2, 3, 4, 5],
      languages: ["en"],
      areaSpecializations: [],
      typeSpecializations: [],
      maxViewingsPerDay: 6,
    });
    const brokerFree = mockBroker({
      id: "broker-free",
      nameEn: "Free Broker",
      workingDays: [1, 2, 3, 4, 5],
      languages: ["en"],
      areaSpecializations: [],
      typeSpecializations: [],
      maxViewingsPerDay: 6,
    });

    mockPrisma.broker.findMany.mockResolvedValue([brokerBusy, brokerFree]);
    mockPrisma.brokerAvailability.findMany.mockResolvedValue([]);
    // Busy has 4, free has 0
    mockPrisma.propertyViewing.count
      .mockResolvedValueOnce(4) // broker-busy
      .mockResolvedValueOnce(0); // broker-free
    // getNextAvailableSlots can be ignored, won't reach
    mockPrisma.propertyViewing.findFirst.mockResolvedValue(null);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-16",
        requestedTime: "10:00",
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(true);
    // brokerBusy: workload = max(0, 20 - 4*4) = 4
    // brokerFree: workload = max(0, 20 - 0*4) = 20
    expect(data.assignedBroker.brokerId).toBe("broker-free");
    expect(data.assignedBroker.reasons).toContain("Light schedule today");
  });

  it("returns nextSlots when no brokers available", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "West Bay",
      listingBrokerId: null,
    });

    // All brokers work Mon-Fri but requested day is Sunday
    mockPrisma.broker.findMany
      .mockResolvedValueOnce([mockBroker({ workingDays: [1, 2, 3, 4, 5] })])
      // second call from getNextAvailableSlots
      .mockResolvedValueOnce([
        mockBroker({
          id: "broker-1",
          nameEn: "Ali",
          workingDays: [1, 2, 3, 4, 5],
          workStartTime: "09:00",
          workEndTime: "18:00",
          maxViewingsPerDay: 6,
        }),
      ]);

    // For getNextAvailableSlots
    mockPrisma.propertyViewing.count.mockResolvedValue(0);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-15", // Sunday
        requestedTime: "10:00",
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(false);
    expect(data.reason).toContain("No brokers available");
    expect(Array.isArray(data.nextSlots)).toBe(true);
  });

  it("returns 500 on unexpected error", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockRejectedValue(
      new Error("DB connection lost"),
    );

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", validBody),
    );
    expect(res.status).toBe(500);
  });

  it("gives city match score lower than district match", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "The Pearl",
      listingBrokerId: null,
    });

    const districtBroker = mockBroker({
      id: "broker-district",
      nameEn: "District Broker",
      workingDays: [1, 2, 3, 4, 5],
      languages: ["en"],
      areaSpecializations: ["The Pearl"], // district match: +25
      typeSpecializations: [],
    });
    const cityBroker = mockBroker({
      id: "broker-city",
      nameEn: "City Broker",
      workingDays: [1, 2, 3, 4, 5],
      languages: ["en"],
      areaSpecializations: ["Doha"], // city match: +15
      typeSpecializations: [],
    });

    mockPrisma.broker.findMany.mockResolvedValue([cityBroker, districtBroker]);
    mockPrisma.brokerAvailability.findMany.mockResolvedValue([]);
    mockPrisma.propertyViewing.count.mockResolvedValue(0);
    mockPrisma.propertyViewing.findFirst.mockResolvedValue(null);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-16",
        requestedTime: "10:00",
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(true);
    // District broker: 25 (district) + 20 (workload) = 45
    // City broker: 15 (city) + 20 (workload) = 35
    expect(data.assignedBroker.brokerId).toBe("broker-district");
  });

  it("limits alternatives to 3 brokers", async () => {
    authenticateAs();
    mockPrisma.property.findFirst.mockResolvedValue({
      id: "prop-1",
      propertyType: "apartment",
      city: "Doha",
      district: "Lusail",
      listingBrokerId: null,
    });

    const brokers = Array.from({ length: 6 }, (_, i) =>
      mockBroker({
        id: `broker-${i}`,
        nameEn: `Broker ${i}`,
        workingDays: [1, 2, 3, 4, 5],
        languages: ["en"],
        areaSpecializations: [],
        typeSpecializations: [],
      }),
    );

    mockPrisma.broker.findMany.mockResolvedValue(brokers);
    mockPrisma.brokerAvailability.findMany.mockResolvedValue([]);
    mockPrisma.propertyViewing.count.mockResolvedValue(0);
    mockPrisma.propertyViewing.findFirst.mockResolvedValue(null);

    const res = await mod.POST(
      makeReq("/api/real-estate/brokers/assign", "POST", {
        propertyId: "prop-1",
        requestedDate: "2026-03-16",
        requestedTime: "10:00",
      }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.available).toBe(true);
    // alternatives = scoredBrokers.slice(1, 4) => max 3
    expect(data.alternatives.length).toBeLessThanOrEqual(3);
  });
});
