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

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

vi.mock("next-auth", () => ({ getServerSession: mockGetServerSession }));
vi.mock("@/app/api/auth/[...nextauth]/authOptions", () => ({
  AuthOptions: {},
}));
vi.mock("@/app/lib/hubspot/dealSync", () => ({
  syncSurveyRequestToHubSpot: mockSyncSurveyRequestToHubSpot,
}));
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/survey_requests", () => ({
  surveyRequests: {
    id: {}, hubspotDealId: {}, portfolioId: {}, notes: {},
    surveyType: {}, status: {}, requestedBy: {}, requestedAt: {}, fulfilledAt: {},
  },
}));
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; },
    get insert() { return mockDbInsert; },
  },
}));

// ── 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 makeInsertChain(returningResult: unknown[] = []) {
  const returning = vi.fn(() => Promise.resolve(returningResult));
  const values = vi.fn(() => ({ returning }));
  return { values };
}

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

// ── Subject under test ────────────────────────────────────────────────────────
import { POST } from "./route";

describe("POST /survey-requests", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockSyncSurveyRequestToHubSpot.mockResolvedValue({ ok: true });
  });

  it("returns 401 when unauthenticated", async () => {
    mockGetServerSession.mockResolvedValue(null);
    const { req, params } = makeRequest({ hubspotDealId: "deal-1", surveyType: "technical_building_survey" });
    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" } });
    // user lookup
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ id: 1n, email: "write@test.com" }]));
    // portfolio role check
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ role: "write" }]));
    // capability check — no rows (directResult), so not an approver
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([], []));

    const { req, params } = makeRequest({ hubspotDealId: "deal-1", surveyType: "technical_building_survey" });
    const res = await POST(req, { params });
    expect(res.status).toBe(403);
    const json = await res.json();
    expect(json.error).toMatch(/approver/i);
  });

  it("returns 409 when a pending request already exists for the deal", async () => {
    mockGetServerSession.mockResolvedValue({ user: { email: "approver@test.com" } });
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ id: 2n, email: "approver@test.com" }]));
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ role: "admin" }]));
    // capability rows come back via directResult (no .limit() on that query)
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([], [{ capability: "approver" }]));
    // pending check — returns a pending row
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ id: 99n, status: "pending" }]));

    const { req, params } = makeRequest({ hubspotDealId: "deal-1", surveyType: "technical_building_survey" });
    const res = await POST(req, { params });
    expect(res.status).toBe(409);
    const json = await res.json();
    expect(json.error).toMatch(/pending/i);
  });

  it("creates the request with surveyType and syncs to HubSpot", async () => {
    const insertedAt = new Date("2026-05-06T10:00:00.000Z");
    mockGetServerSession.mockResolvedValue({ user: { email: "approver@test.com" } });
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ id: 2n, email: "approver@test.com" }]));
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ role: "admin" }]));
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([], [{ capability: "approver" }]));
    // no pending request
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([]));
    // insert returning
    mockDbInsert.mockImplementationOnce(() =>
      makeInsertChain([{ id: 42n, requestedAt: insertedAt }])
    );

    const { req, params } = makeRequest({ hubspotDealId: "deal-abc", surveyType: "technical_building_survey" });
    const res = await POST(req, { params });
    expect(res.status).toBe(200);
    const json = await res.json();
    expect(json.ok).toBe(true);
    expect(json.id).toBe("42");
    expect(json.hubspotSync).toBe("ok");

    expect(mockSyncSurveyRequestToHubSpot).toHaveBeenCalledWith({
      hubspotDealId: "deal-abc",
      surveyType: "technical_building_survey",
      requestedAt: insertedAt,
    });
  });

  it("returns hubspotSync: failed but still 200 when HubSpot fails", async () => {
    const insertedAt = new Date();
    mockGetServerSession.mockResolvedValue({ user: { email: "approver@test.com" } });
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ id: 2n, email: "approver@test.com" }]));
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ role: "admin" }]));
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([], [{ capability: "approver" }]));
    mockDbSelect.mockImplementationOnce(() => makeSelectChain([]));
    mockDbInsert.mockImplementationOnce(() =>
      makeInsertChain([{ id: 43n, requestedAt: insertedAt }])
    );
    mockSyncSurveyRequestToHubSpot.mockResolvedValue({ ok: false, error: "HubSpot sync failed" });

    const { req, params } = makeRequest({ hubspotDealId: "deal-abc", surveyType: "technical_building_survey" });
    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("failed");
    expect(json.hubspotError).toBe("HubSpot sync failed");
  });
});
