import { describe, it, expect, vi, beforeEach } from "vitest";
import { NextRequest } from "next/server";

// ── Hoisted mocks ─────────────────────────────────────────────────────────────
const {
  mockGetServerSession,
  mockCreatePibiRequests,
  mockDbSelect,
} = vi.hoisted(() => ({
  mockGetServerSession: vi.fn(),
  mockCreatePibiRequests: vi.fn(),
  mockDbSelect: vi.fn(),
}));

vi.mock("next-auth", () => ({ getServerSession: mockGetServerSession }));
vi.mock("@/app/api/auth/[...nextauth]/authOptions", () => ({ AuthOptions: {} }));
vi.mock("@/app/lib/createPibiRequests", () => ({
  createPibiRequests: mockCreatePibiRequests,
  PIBI_ORDERED_TEXT_PROP: "measures_for_pibi_ordered_text",
}));
vi.mock("drizzle-orm", () => ({
  and: vi.fn((...args: unknown[]) => ({ $and: args })),
  eq: vi.fn((a: unknown, b: unknown) => ({ $eq: [a, b] })),
  desc: vi.fn((col: unknown) => ({ $desc: col })),
}));
vi.mock("@/app/db/schema/pibi_requests", () => ({
  pibiRequests: {
    id: {}, hubspotDealId: {}, portfolioId: {}, measureName: {},
    orderedAt: {}, completedAt: {}, createdByUserId: {}, pushedAt: {},
  },
}));
vi.mock("@/app/db/schema/portfolio", () => ({
  portfolioUsers: { portfolioId: {}, userId: {}, role: {} },
  portfolioCapabilities: { portfolioId: {}, userId: {}, capability: {} },
}));
vi.mock("@/app/db/schema/users", () => ({ user: { id: {}, email: {} } }));
vi.mock("@/app/db/db", () => ({
  db: { get select() { return mockDbSelect; } },
}));

// ── Helpers ───────────────────────────────────────────────────────────────────
function makeSelectChain(limitResult: unknown[], directResult: unknown[] = []) {
  const self: Record<string, unknown> = {};
  self["then"] = (_resolve: (v: unknown) => unknown, _reject: (e: unknown) => unknown) =>
    Promise.resolve(directResult).then(_resolve, _reject);
  self["from"] = vi.fn(() => self);
  self["innerJoin"] = vi.fn(() => self);
  self["where"] = vi.fn(() => self);
  self["orderBy"] = vi.fn(() => self);
  self["limit"] = vi.fn(() => Promise.resolve(limitResult));
  return self;
}

function mockApproverAuth(userId = 2n) {
  mockGetServerSession.mockResolvedValue({ user: { email: "approver@test.com" } });
  mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ id: userId, email: "approver@test.com" }]));
  mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ role: "admin" }]));
  mockDbSelect.mockImplementationOnce(() => makeSelectChain([], [{ capability: "approver" }]));
}

function makeRequest(body: unknown, portfolioId = "5") {
  const req = new NextRequest(
    `http://localhost/api/portfolio/${portfolioId}/pibi-requests`,
    { method: "POST", body: JSON.stringify(body), headers: { "content-type": "application/json" } },
  );
  return { req, params: Promise.resolve({ portfolioId }) };
}

function makeGetRequest(dealId: string, portfolioId = "5") {
  const req = new NextRequest(
    `http://localhost/api/portfolio/${portfolioId}/pibi-requests?dealId=${dealId}`,
  );
  return { req, params: Promise.resolve({ portfolioId }) };
}

import { GET, POST } from "./route";

describe("GET /pibi-requests", () => {
  beforeEach(() => vi.clearAllMocks());

  it("returns 401 when unauthenticated", async () => {
    mockGetServerSession.mockResolvedValue(null);
    const { req, params } = makeGetRequest("deal-1");
    const res = await GET(req, { params });
    expect(res.status).toBe(401);
  });

  it("returns 400 when dealId missing", async () => {
    mockGetServerSession.mockResolvedValue({ user: { email: "x@test.com" } });
    const req = new NextRequest("http://localhost/api/portfolio/5/pibi-requests");
    const res = await GET(req, { params: Promise.resolve({ portfolioId: "5" }) });
    expect(res.status).toBe(400);
  });

  it("returns pibi requests for deal", async () => {
    const orderedAt = new Date("2026-05-06T10:00:00Z");
    mockGetServerSession.mockResolvedValue({ user: { email: "x@test.com" } });
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ id: 1n }]));
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ role: "admin" }]));
    mockDbSelect.mockImplementationOnce(() =>
      makeSelectChain([], [
        { id: 1n, measureName: "CWI", orderedAt, completedAt: null },
        { id: 2n, measureName: "Loft insulation", orderedAt, completedAt: null },
      ])
    );

    const { req, params } = makeGetRequest("deal-1");
    const res = await GET(req, { params });
    expect(res.status).toBe(200);
    const json = await res.json();
    expect(json.pibiRequests).toHaveLength(2);
  });
});

describe("POST /pibi-requests", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockCreatePibiRequests.mockResolvedValue({ ok: true, insertedRowIds: [1n], hubspotSync: "ok" });
  });

  it("returns 401 when unauthenticated", async () => {
    mockGetServerSession.mockResolvedValue(null);
    const { req, params } = makeRequest({ dealId: "deal-1", measureNames: ["CWI"] });
    const res = await POST(req, { params });
    expect(res.status).toBe(401);
  });

  it("returns 403 when user lacks approver capability", async () => {
    mockGetServerSession.mockResolvedValue({ user: { email: "write@test.com" } });
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ id: 1n }]));
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ role: "write" }]));
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([], []));

    const { req, params } = makeRequest({ dealId: "deal-1", measureNames: ["CWI"] });
    const res = await POST(req, { params });
    expect(res.status).toBe(403);
  });

  it("returns 400 when measureNames is empty", async () => {
    mockGetServerSession.mockResolvedValue({ user: { email: "approver@test.com" } });
    const { req, params } = makeRequest({ dealId: "deal-1", measureNames: [] });
    const res = await POST(req, { params });
    expect(res.status).toBe(400);
  });

  it("creates PIBIs and returns ok=true with insertedCount", async () => {
    mockApproverAuth();

    const { req, params } = makeRequest({ dealId: "deal-1", measureNames: ["CWI", "ASHP"] });
    const res = await POST(req, { params });
    expect(res.status).toBe(200);
    const json = await res.json();
    expect(json.ok).toBe(true);
    expect(json.hubspotSync).toBe("ok");
    expect(json.insertedCount).toBe(1);
  });
});
