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

const {
  mockGetServerSession,
  mockUpdatePibiRequest,
  mockDeletePibiRequest,
  mockDbSelect,
} = vi.hoisted(() => ({
  mockGetServerSession: vi.fn(),
  mockUpdatePibiRequest: vi.fn(),
  mockDeletePibiRequest: vi.fn(),
  mockDbSelect: vi.fn(),
}));

vi.mock("next-auth", () => ({ getServerSession: mockGetServerSession }));
vi.mock("@/app/api/auth/[...nextauth]/authOptions", () => ({ AuthOptions: {} }));
vi.mock("@/app/lib/updatePibiRequest", () => ({
  updatePibiRequest: mockUpdatePibiRequest,
  PIBI_ORDERED_TEXT_PROP: "measures_for_pibi_ordered_text",
}));
vi.mock("@/app/lib/deletePibiRequest", () => ({
  deletePibiRequest: mockDeletePibiRequest,
}));
vi.mock("drizzle-orm", () => ({
  and: vi.fn((...args: unknown[]) => ({ $and: args })),
  eq: vi.fn((a: unknown, b: unknown) => ({ $eq: [a, b] })),
}));
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; } },
}));

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["where"] = 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 }]));
  mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ role: "admin" }]));
  mockDbSelect.mockImplementationOnce(() => makeSelectChain([], [{ capability: "approver" }]));
}

function makeParams(portfolioId = "5", id = "7") {
  return Promise.resolve({ portfolioId, id });
}

import { PATCH, DELETE } from "./route";

describe("PATCH /pibi-requests/[id]", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockUpdatePibiRequest.mockResolvedValue({ ok: true, hubspotSync: "ok" });
  });

  it("returns 401 when unauthenticated", async () => {
    mockGetServerSession.mockResolvedValue(null);
    const req = new NextRequest("http://localhost/api/portfolio/5/pibi-requests/7", {
      method: "PATCH",
      body: JSON.stringify({ dealId: "deal-1", completedAt: "2026-05-10T00:00:00Z" }),
      headers: { "content-type": "application/json" },
    });
    const res = await PATCH(req, { params: makeParams() });
    expect(res.status).toBe(401);
  });

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

    const req = new NextRequest("http://localhost/api/portfolio/5/pibi-requests/7", {
      method: "PATCH",
      body: JSON.stringify({ dealId: "deal-1", completedAt: "2026-05-10T00:00:00Z" }),
      headers: { "content-type": "application/json" },
    });
    const res = await PATCH(req, { params: makeParams() });
    expect(res.status).toBe(403);
  });

  it("updates PIBI and returns ok=true", async () => {
    mockApproverAuth();
    const req = new NextRequest("http://localhost/api/portfolio/5/pibi-requests/7", {
      method: "PATCH",
      body: JSON.stringify({ dealId: "deal-1", completedAt: "2026-05-10T00:00:00Z" }),
      headers: { "content-type": "application/json" },
    });
    const res = await PATCH(req, { params: makeParams() });
    expect(res.status).toBe(200);
    const json = await res.json();
    expect(json.ok).toBe(true);
    expect(json.hubspotSync).toBe("ok");
    expect(mockUpdatePibiRequest).toHaveBeenCalledWith(
      expect.objectContaining({ id: 7n, dealId: "deal-1" }),
    );
  });
});

describe("DELETE /pibi-requests/[id]", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockDeletePibiRequest.mockResolvedValue({ ok: true, hubspotSync: "ok" });
  });

  it("returns 401 when unauthenticated", async () => {
    mockGetServerSession.mockResolvedValue(null);
    const req = new NextRequest("http://localhost/api/portfolio/5/pibi-requests/7?dealId=deal-1", {
      method: "DELETE",
    });
    const res = await DELETE(req, { params: makeParams() });
    expect(res.status).toBe(401);
  });

  it("deletes PIBI and returns ok=true", async () => {
    mockApproverAuth();
    const req = new NextRequest("http://localhost/api/portfolio/5/pibi-requests/7?dealId=deal-1", {
      method: "DELETE",
    });
    const res = await DELETE(req, { params: makeParams() });
    expect(res.status).toBe(200);
    const json = await res.json();
    expect(json.ok).toBe(true);
    expect(mockDeletePibiRequest).toHaveBeenCalledWith(
      expect.objectContaining({ id: 7n, dealId: "deal-1" }),
    );
  });
});
