import { beforeEach, describe, expect, it, vi } from "vitest";

const { mockOptima, mockJson, mockError } = vi.hoisted(() => ({
  mockOptima: {
    sales: {
      updateNote: vi.fn(),
      deleteNote: vi.fn(),
    },
  },
  mockJson: vi.fn((data, init?) => {
    return new Response(JSON.stringify(data), {
      status: init?.status ?? 200,
    });
  }),
  mockError: vi.fn((status: number, message: string) => {
    throw { status, body: { message } };
  }),
}));

vi.mock("$lib", () => ({ optima: mockOptima }));
vi.mock("@sveltejs/kit", () => ({ json: mockJson, error: mockError }));

import { PATCH, DELETE } from "./+server";

describe("/sales/opportunity/[id]/notes/[noteId]", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.spyOn(console, "error").mockImplementation(() => {});
  });

  describe("PATCH", () => {
    it("throws 401 when no access token", async () => {
      const event = {
        locals: {},
        params: { id: "opp-1", noteId: "5" },
        request: { json: vi.fn().mockResolvedValue({ text: "updated" }) },
      };
      await expect(PATCH(event as any)).rejects.toBeDefined();
    });

    it("throws 400 for invalid noteId", async () => {
      const event = {
        locals: { session: { accessToken: "tok" } },
        params: { id: "opp-1", noteId: "abc" },
        request: { json: vi.fn().mockResolvedValue({ text: "updated" }) },
      };
      await expect(PATCH(event as any)).rejects.toEqual(
        expect.objectContaining({ status: 400 }),
      );
    });

    it("throws 400 when neither text nor flagged provided", async () => {
      const event = {
        locals: { session: { accessToken: "tok" } },
        params: { id: "opp-1", noteId: "5" },
        request: { json: vi.fn().mockResolvedValue({}) },
      };
      await expect(PATCH(event as any)).rejects.toEqual(
        expect.objectContaining({ status: 400 }),
      );
    });

    it("updates note text", async () => {
      mockOptima.sales.updateNote.mockResolvedValueOnce({ id: 5 });

      const event = {
        locals: { session: { accessToken: "tok" } },
        params: { id: "opp-1", noteId: "5" },
        request: {
          json: vi.fn().mockResolvedValue({ text: "updated text" }),
        },
      };

      await PATCH(event as any);

      expect(mockOptima.sales.updateNote).toHaveBeenCalledWith(
        "tok",
        "opp-1",
        5,
        { text: "updated text" },
      );
    });

    it("updates note flagged status", async () => {
      mockOptima.sales.updateNote.mockResolvedValueOnce({ id: 5 });

      const event = {
        locals: { session: { accessToken: "tok" } },
        params: { id: "opp-1", noteId: "5" },
        request: {
          json: vi.fn().mockResolvedValue({ flagged: true }),
        },
      };

      await PATCH(event as any);

      expect(mockOptima.sales.updateNote).toHaveBeenCalledWith(
        "tok",
        "opp-1",
        5,
        { flagged: true },
      );
    });
  });

  describe("DELETE", () => {
    it("throws 401 when no access token", async () => {
      const event = {
        locals: {},
        params: { id: "opp-1", noteId: "5" },
      };
      await expect(DELETE(event as any)).rejects.toBeDefined();
    });

    it("throws 400 for invalid noteId", async () => {
      const event = {
        locals: { session: { accessToken: "tok" } },
        params: { id: "opp-1", noteId: "abc" },
      };
      await expect(DELETE(event as any)).rejects.toEqual(
        expect.objectContaining({ status: 400 }),
      );
    });

    it("deletes note successfully", async () => {
      mockOptima.sales.deleteNote.mockResolvedValueOnce({ success: true });

      const event = {
        locals: { session: { accessToken: "tok" } },
        params: { id: "opp-1", noteId: "5" },
      };

      await DELETE(event as any);

      expect(mockOptima.sales.deleteNote).toHaveBeenCalledWith(
        "tok",
        "opp-1",
        5,
      );
      expect(mockJson).toHaveBeenCalledWith({ success: true });
    });
  });
});
