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

const {
  mockFindConnector,
  mockFindChats,
  mockDeleteWhere,
  mockDelete,
  mockUpdateWhere,
  mockUpdateSet,
  mockUpdate,
  mockPragmaAll,
  mockLegacyRun,
  mockPrepare,
} = vi.hoisted(() => {
  const mockFindConnector = vi.fn();
  const mockFindChats = vi.fn();
  const mockDeleteWhere = vi.fn();
  const mockDelete = vi.fn(() => ({
    where: mockDeleteWhere,
  }));
  const mockUpdateWhere = vi.fn();
  const mockUpdateSet = vi.fn(() => ({
    where: mockUpdateWhere,
  }));
  const mockUpdate = vi.fn(() => ({
    set: mockUpdateSet,
  }));
  const mockPragmaAll = vi.fn();
  const mockLegacyRun = vi.fn();
  const mockPrepare = vi.fn((sql: string) => {
    if (sql === "PRAGMA table_info(apps)") {
      return { all: mockPragmaAll };
    }

    if (sql === "UPDATE apps SET connector_id = NULL WHERE connector_id = ?") {
      return { run: mockLegacyRun };
    }

    throw new Error(`Unexpected SQL: ${sql}`);
  });

  return {
    mockFindConnector,
    mockFindChats,
    mockDeleteWhere,
    mockDelete,
    mockUpdateWhere,
    mockUpdateSet,
    mockUpdate,
    mockPragmaAll,
    mockLegacyRun,
    mockPrepare,
  };
});

vi.mock("../db", () => ({
  db: {
    query: {
      connectors: {
        findFirst: mockFindConnector,
      },
      chats: {
        findMany: mockFindChats,
      },
    },
    update: mockUpdate,
    delete: mockDelete,
    $client: {
      prepare: mockPrepare,
    },
  },
}));

import { deleteConnectorWithCleanup } from "../ipc/handlers/connector_handlers";
import { DyadError } from "@/errors/dyad_error";

describe("deleteConnectorWithCleanup", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockDeleteWhere.mockResolvedValue(undefined);
    mockUpdateWhere.mockResolvedValue(undefined);
  });

  it("cleans up legacy app references and chat connector ids before delete", async () => {
    mockFindConnector.mockResolvedValue({
      id: 3,
      name: "Test Connector",
    });
    mockPragmaAll.mockReturnValue([{ name: "id" }, { name: "connector_id" }]);
    mockFindChats.mockResolvedValue([
      { id: 10, connectorIdsJson: [1, 3, 5] },
      { id: 11, connectorIdsJson: [3] },
      { id: 12, connectorIdsJson: [7] },
      { id: 13, connectorIdsJson: null },
    ]);

    await deleteConnectorWithCleanup(3);

    expect(mockLegacyRun).toHaveBeenCalledWith(3);
    expect(mockUpdate).toHaveBeenCalledTimes(2);
    expect(mockUpdateSet).toHaveBeenNthCalledWith(1, {
      connectorIdsJson: [1, 5],
    });
    expect(mockUpdateSet).toHaveBeenNthCalledWith(2, {
      connectorIdsJson: null,
    });
    expect(mockDelete).toHaveBeenCalledTimes(1);
    expect(mockDeleteWhere).toHaveBeenCalledTimes(1);
  });

  it("skips legacy cleanup when the old apps.connector_id column is absent", async () => {
    mockFindConnector.mockResolvedValue({
      id: 4,
      name: "Modern Connector",
    });
    mockPragmaAll.mockReturnValue([{ name: "id" }, { name: "path" }]);
    mockFindChats.mockResolvedValue([]);

    await deleteConnectorWithCleanup(4);

    expect(mockLegacyRun).not.toHaveBeenCalled();
    expect(mockDelete).toHaveBeenCalledTimes(1);
  });

  it("throws a not found error when the connector does not exist", async () => {
    mockFindConnector.mockResolvedValue(undefined);

    const deletion = deleteConnectorWithCleanup(999);

    await expect(deletion).rejects.toBeInstanceOf(DyadError);
    await expect(deletion).rejects.toMatchObject({
      message: "Connector not found",
    });
  });
});
