/**
 * Workspace Isolation Verification Tests
 *
 * SEC-API-4: These tests verify that all multi-tenant services properly include
 * workspaceId filtering in their Prisma queries to ensure tenant isolation.
 *
 * Purpose:
 * - Verify findMany/findFirst queries include workspaceId in where clause
 * - Verify create operations set workspaceId from context
 * - Verify update/delete operations check workspaceId
 * - Use Prisma query spying to verify actual queries include workspaceId
 *
 * Note: This is a VERIFICATION test suite - it tests that workspaceId is properly
 * included in all queries, not that RLS is implemented at the database level.
 */

import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";

// Services under test
import { TasksService } from "../../tasks/tasks.service";
import { ProjectsService } from "../../projects/projects.service";
import { EventsService } from "../../events/events.service";
import { KnowledgeService } from "../../knowledge/knowledge.service";

// Dependencies
import { PrismaService } from "../../prisma/prisma.service";
import { ActivityService } from "../../activity/activity.service";
import { LinkSyncService } from "../../knowledge/services/link-sync.service";
import { KnowledgeCacheService } from "../../knowledge/services/cache.service";
import { EmbeddingService } from "../../knowledge/services/embedding.service";
import { OllamaEmbeddingService } from "../../knowledge/services/ollama-embedding.service";
import { EmbeddingQueueService } from "../../knowledge/queues/embedding-queue.service";

// Types
import { TaskStatus, TaskPriority, ProjectStatus, EntryStatus } from "@prisma/client";
import { NotFoundException } from "@nestjs/common";

/**
 * Test fixture IDs
 */
const WORKSPACE_A = "workspace-a-550e8400-e29b-41d4-a716-446655440001";
const WORKSPACE_B = "workspace-b-550e8400-e29b-41d4-a716-446655440002";
const USER_ID = "user-550e8400-e29b-41d4-a716-446655440003";
const ENTITY_ID = "entity-550e8400-e29b-41d4-a716-446655440004";

describe("SEC-API-4: Workspace Isolation Verification", () => {
  /**
   * ============================================================================
   * TASKS SERVICE - Workspace Isolation Tests
   * ============================================================================
   */
  describe("TasksService - Workspace Isolation", () => {
    let service: TasksService;
    let mockPrismaService: Record<string, unknown>;
    let mockActivityService: Record<string, unknown>;

    beforeEach(async () => {
      mockPrismaService = {
        task: {
          create: vi.fn(),
          findMany: vi.fn(),
          count: vi.fn(),
          findUnique: vi.fn(),
          update: vi.fn(),
          delete: vi.fn(),
        },
      };

      mockActivityService = {
        logTaskCreated: vi.fn().mockResolvedValue({}),
        logTaskUpdated: vi.fn().mockResolvedValue({}),
        logTaskDeleted: vi.fn().mockResolvedValue({}),
        logTaskCompleted: vi.fn().mockResolvedValue({}),
        logTaskAssigned: vi.fn().mockResolvedValue({}),
      };

      const module: TestingModule = await Test.createTestingModule({
        providers: [
          TasksService,
          { provide: PrismaService, useValue: mockPrismaService },
          { provide: ActivityService, useValue: mockActivityService },
        ],
      }).compile();

      service = module.get<TasksService>(TasksService);
      vi.clearAllMocks();
    });

    describe("create() - workspaceId binding", () => {
      it("should connect task to provided workspaceId", async () => {
        const mockTask = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          title: "Test Task",
          status: TaskStatus.NOT_STARTED,
          priority: TaskPriority.MEDIUM,
          creatorId: USER_ID,
          assigneeId: null,
          projectId: null,
          parentId: null,
          description: null,
          dueDate: null,
          sortOrder: 0,
          metadata: {},
          createdAt: new Date(),
          updatedAt: new Date(),
          completedAt: null,
        };

        (mockPrismaService.task as Record<string, unknown>).create = vi
          .fn()
          .mockResolvedValue(mockTask);

        await service.create(WORKSPACE_A, USER_ID, { title: "Test Task" });

        expect(mockPrismaService.task.create).toHaveBeenCalledWith(
          expect.objectContaining({
            data: expect.objectContaining({
              workspace: { connect: { id: WORKSPACE_A } },
            }),
          })
        );
      });

      it("should NOT allow task creation without workspaceId binding", async () => {
        const createCall = (mockPrismaService.task as Record<string, unknown>).create as ReturnType<
          typeof vi.fn
        >;
        createCall.mockResolvedValue({
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          title: "Test",
        });

        await service.create(WORKSPACE_A, USER_ID, { title: "Test" });

        // Verify the create call explicitly includes workspace connection
        const callArgs = createCall.mock.calls[0][0];
        expect(callArgs.data.workspace).toBeDefined();
        expect(callArgs.data.workspace.connect.id).toBe(WORKSPACE_A);
      });
    });

    describe("findAll() - workspaceId filtering", () => {
      it("should include workspaceId in where clause when provided", async () => {
        (mockPrismaService.task as Record<string, unknown>).findMany = vi
          .fn()
          .mockResolvedValue([]);
        (mockPrismaService.task as Record<string, unknown>).count = vi.fn().mockResolvedValue(0);

        await service.findAll({ workspaceId: WORKSPACE_A });

        expect(mockPrismaService.task.findMany).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              workspaceId: WORKSPACE_A,
            }),
          })
        );

        expect(mockPrismaService.task.count).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              workspaceId: WORKSPACE_A,
            }),
          })
        );
      });

      it("should maintain workspaceId filter when combined with other filters", async () => {
        (mockPrismaService.task as Record<string, unknown>).findMany = vi
          .fn()
          .mockResolvedValue([]);
        (mockPrismaService.task as Record<string, unknown>).count = vi.fn().mockResolvedValue(0);

        await service.findAll({
          workspaceId: WORKSPACE_A,
          status: TaskStatus.IN_PROGRESS,
          priority: TaskPriority.HIGH,
        });

        const findManyCall = (mockPrismaService.task as Record<string, unknown>)
          .findMany as ReturnType<typeof vi.fn>;
        const whereClause = findManyCall.mock.calls[0][0].where;

        expect(whereClause.workspaceId).toBe(WORKSPACE_A);
        expect(whereClause.status).toBe(TaskStatus.IN_PROGRESS);
        expect(whereClause.priority).toBe(TaskPriority.HIGH);
      });

      it("should use empty where clause if workspaceId not provided (SECURITY CONCERN)", async () => {
        // NOTE: This test documents current behavior - findAll accepts queries without workspaceId
        // This is a potential security issue that should be addressed
        (mockPrismaService.task as Record<string, unknown>).findMany = vi
          .fn()
          .mockResolvedValue([]);
        (mockPrismaService.task as Record<string, unknown>).count = vi.fn().mockResolvedValue(0);

        await service.findAll({});

        const findManyCall = (mockPrismaService.task as Record<string, unknown>)
          .findMany as ReturnType<typeof vi.fn>;
        const whereClause = findManyCall.mock.calls[0][0].where;

        // Document that empty query leads to empty where clause
        expect(whereClause).toEqual({});
      });
    });

    describe("findOne() - workspaceId filtering", () => {
      it("should include workspaceId in findUnique query", async () => {
        const mockTask = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          title: "Test",
          subtasks: [],
        };
        (mockPrismaService.task as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockTask);

        await service.findOne(ENTITY_ID, WORKSPACE_A);

        expect(mockPrismaService.task.findUnique).toHaveBeenCalledWith(
          expect.objectContaining({
            where: {
              id: ENTITY_ID,
              workspaceId: WORKSPACE_A,
            },
          })
        );
      });

      it("should NOT return task from different workspace", async () => {
        (mockPrismaService.task as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        await expect(service.findOne(ENTITY_ID, WORKSPACE_B)).rejects.toThrow(NotFoundException);

        // Verify query was scoped to WORKSPACE_B
        expect(mockPrismaService.task.findUnique).toHaveBeenCalledWith(
          expect.objectContaining({
            where: {
              id: ENTITY_ID,
              workspaceId: WORKSPACE_B,
            },
          })
        );
      });
    });

    describe("update() - workspaceId filtering", () => {
      it("should verify task belongs to workspace before update", async () => {
        const mockTask = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          title: "Original",
          status: TaskStatus.NOT_STARTED,
        };
        (mockPrismaService.task as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockTask);
        (mockPrismaService.task as Record<string, unknown>).update = vi
          .fn()
          .mockResolvedValue({ ...mockTask, title: "Updated" });

        await service.update(ENTITY_ID, WORKSPACE_A, USER_ID, { title: "Updated" });

        // Verify lookup includes workspaceId
        expect(mockPrismaService.task.findUnique).toHaveBeenCalledWith({
          where: { id: ENTITY_ID, workspaceId: WORKSPACE_A },
        });

        // Verify update includes workspaceId
        expect(mockPrismaService.task.update).toHaveBeenCalledWith(
          expect.objectContaining({
            where: {
              id: ENTITY_ID,
              workspaceId: WORKSPACE_A,
            },
          })
        );
      });

      it("should reject update for task in different workspace", async () => {
        (mockPrismaService.task as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        await expect(
          service.update(ENTITY_ID, WORKSPACE_B, USER_ID, { title: "Hacked" })
        ).rejects.toThrow(NotFoundException);

        expect(mockPrismaService.task.update).not.toHaveBeenCalled();
      });
    });

    describe("remove() - workspaceId filtering", () => {
      it("should verify task belongs to workspace before delete", async () => {
        const mockTask = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          title: "To Delete",
        };
        (mockPrismaService.task as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockTask);
        (mockPrismaService.task as Record<string, unknown>).delete = vi
          .fn()
          .mockResolvedValue(mockTask);

        await service.remove(ENTITY_ID, WORKSPACE_A, USER_ID);

        // Verify lookup includes workspaceId
        expect(mockPrismaService.task.findUnique).toHaveBeenCalledWith({
          where: { id: ENTITY_ID, workspaceId: WORKSPACE_A },
        });

        // Verify delete includes workspaceId
        expect(mockPrismaService.task.delete).toHaveBeenCalledWith({
          where: {
            id: ENTITY_ID,
            workspaceId: WORKSPACE_A,
          },
        });
      });

      it("should reject delete for task in different workspace", async () => {
        (mockPrismaService.task as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        await expect(service.remove(ENTITY_ID, WORKSPACE_B, USER_ID)).rejects.toThrow(
          NotFoundException
        );

        expect(mockPrismaService.task.delete).not.toHaveBeenCalled();
      });
    });
  });

  /**
   * ============================================================================
   * PROJECTS SERVICE - Workspace Isolation Tests
   * ============================================================================
   */
  describe("ProjectsService - Workspace Isolation", () => {
    let service: ProjectsService;
    let mockPrismaService: Record<string, unknown>;
    let mockActivityService: Record<string, unknown>;

    beforeEach(async () => {
      mockPrismaService = {
        project: {
          create: vi.fn(),
          findMany: vi.fn(),
          count: vi.fn(),
          findUnique: vi.fn(),
          update: vi.fn(),
          delete: vi.fn(),
        },
      };

      mockActivityService = {
        logProjectCreated: vi.fn().mockResolvedValue({}),
        logProjectUpdated: vi.fn().mockResolvedValue({}),
        logProjectDeleted: vi.fn().mockResolvedValue({}),
      };

      const module: TestingModule = await Test.createTestingModule({
        providers: [
          ProjectsService,
          { provide: PrismaService, useValue: mockPrismaService },
          { provide: ActivityService, useValue: mockActivityService },
        ],
      }).compile();

      service = module.get<ProjectsService>(ProjectsService);
      vi.clearAllMocks();
    });

    describe("create() - workspaceId binding", () => {
      it("should connect project to provided workspaceId", async () => {
        const mockProject = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          name: "Test Project",
          status: ProjectStatus.PLANNING,
          creatorId: USER_ID,
          description: null,
          color: null,
          startDate: null,
          endDate: null,
          metadata: {},
          createdAt: new Date(),
          updatedAt: new Date(),
        };

        (mockPrismaService.project as Record<string, unknown>).create = vi
          .fn()
          .mockResolvedValue(mockProject);

        await service.create(WORKSPACE_A, USER_ID, { name: "Test Project" });

        expect(mockPrismaService.project.create).toHaveBeenCalledWith(
          expect.objectContaining({
            data: expect.objectContaining({
              workspace: { connect: { id: WORKSPACE_A } },
            }),
          })
        );
      });
    });

    describe("findAll() - workspaceId filtering", () => {
      it("should include workspaceId in where clause when provided", async () => {
        (mockPrismaService.project as Record<string, unknown>).findMany = vi
          .fn()
          .mockResolvedValue([]);
        (mockPrismaService.project as Record<string, unknown>).count = vi.fn().mockResolvedValue(0);

        await service.findAll({ workspaceId: WORKSPACE_A });

        expect(mockPrismaService.project.findMany).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              workspaceId: WORKSPACE_A,
            }),
          })
        );
      });

      it("should maintain workspaceId filter with status filter", async () => {
        (mockPrismaService.project as Record<string, unknown>).findMany = vi
          .fn()
          .mockResolvedValue([]);
        (mockPrismaService.project as Record<string, unknown>).count = vi.fn().mockResolvedValue(0);

        await service.findAll({
          workspaceId: WORKSPACE_A,
          status: ProjectStatus.ACTIVE,
        });

        const findManyCall = (mockPrismaService.project as Record<string, unknown>)
          .findMany as ReturnType<typeof vi.fn>;
        const whereClause = findManyCall.mock.calls[0][0].where;

        expect(whereClause.workspaceId).toBe(WORKSPACE_A);
        expect(whereClause.status).toBe(ProjectStatus.ACTIVE);
      });
    });

    describe("findOne() - workspaceId filtering", () => {
      it("should include workspaceId in findUnique query", async () => {
        const mockProject = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          name: "Test",
          tasks: [],
          events: [],
          _count: { tasks: 0, events: 0 },
        };
        (mockPrismaService.project as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockProject);

        await service.findOne(ENTITY_ID, WORKSPACE_A);

        expect(mockPrismaService.project.findUnique).toHaveBeenCalledWith(
          expect.objectContaining({
            where: {
              id: ENTITY_ID,
              workspaceId: WORKSPACE_A,
            },
          })
        );
      });

      it("should NOT return project from different workspace", async () => {
        (mockPrismaService.project as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        await expect(service.findOne(ENTITY_ID, WORKSPACE_B)).rejects.toThrow(NotFoundException);
      });
    });

    describe("update() - workspaceId filtering", () => {
      it("should verify project belongs to workspace before update", async () => {
        const mockProject = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          name: "Original",
          status: ProjectStatus.PLANNING,
        };
        (mockPrismaService.project as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockProject);
        (mockPrismaService.project as Record<string, unknown>).update = vi
          .fn()
          .mockResolvedValue({ ...mockProject, name: "Updated" });

        await service.update(ENTITY_ID, WORKSPACE_A, USER_ID, { name: "Updated" });

        expect(mockPrismaService.project.update).toHaveBeenCalledWith(
          expect.objectContaining({
            where: {
              id: ENTITY_ID,
              workspaceId: WORKSPACE_A,
            },
          })
        );
      });

      it("should reject update for project in different workspace", async () => {
        (mockPrismaService.project as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        await expect(
          service.update(ENTITY_ID, WORKSPACE_B, USER_ID, { name: "Hacked" })
        ).rejects.toThrow(NotFoundException);

        expect(mockPrismaService.project.update).not.toHaveBeenCalled();
      });
    });

    describe("remove() - workspaceId filtering", () => {
      it("should verify project belongs to workspace before delete", async () => {
        const mockProject = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          name: "To Delete",
        };
        (mockPrismaService.project as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockProject);
        (mockPrismaService.project as Record<string, unknown>).delete = vi
          .fn()
          .mockResolvedValue(mockProject);

        await service.remove(ENTITY_ID, WORKSPACE_A, USER_ID);

        expect(mockPrismaService.project.delete).toHaveBeenCalledWith({
          where: {
            id: ENTITY_ID,
            workspaceId: WORKSPACE_A,
          },
        });
      });
    });
  });

  /**
   * ============================================================================
   * EVENTS SERVICE - Workspace Isolation Tests
   * ============================================================================
   */
  describe("EventsService - Workspace Isolation", () => {
    let service: EventsService;
    let mockPrismaService: Record<string, unknown>;
    let mockActivityService: Record<string, unknown>;

    beforeEach(async () => {
      mockPrismaService = {
        event: {
          create: vi.fn(),
          findMany: vi.fn(),
          count: vi.fn(),
          findUnique: vi.fn(),
          update: vi.fn(),
          delete: vi.fn(),
        },
      };

      mockActivityService = {
        logEventCreated: vi.fn().mockResolvedValue({}),
        logEventUpdated: vi.fn().mockResolvedValue({}),
        logEventDeleted: vi.fn().mockResolvedValue({}),
      };

      const module: TestingModule = await Test.createTestingModule({
        providers: [
          EventsService,
          { provide: PrismaService, useValue: mockPrismaService },
          { provide: ActivityService, useValue: mockActivityService },
        ],
      }).compile();

      service = module.get<EventsService>(EventsService);
      vi.clearAllMocks();
    });

    describe("create() - workspaceId binding", () => {
      it("should connect event to provided workspaceId", async () => {
        const mockEvent = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          title: "Test Event",
          startTime: new Date(),
          creatorId: USER_ID,
          description: null,
          endTime: null,
          location: null,
          allDay: false,
          recurrence: null,
          projectId: null,
          metadata: {},
          createdAt: new Date(),
          updatedAt: new Date(),
        };

        (mockPrismaService.event as Record<string, unknown>).create = vi
          .fn()
          .mockResolvedValue(mockEvent);

        await service.create(WORKSPACE_A, USER_ID, {
          title: "Test Event",
          startTime: new Date(),
        });

        expect(mockPrismaService.event.create).toHaveBeenCalledWith(
          expect.objectContaining({
            data: expect.objectContaining({
              workspace: { connect: { id: WORKSPACE_A } },
            }),
          })
        );
      });
    });

    describe("findAll() - workspaceId filtering", () => {
      it("should include workspaceId in where clause when provided", async () => {
        (mockPrismaService.event as Record<string, unknown>).findMany = vi
          .fn()
          .mockResolvedValue([]);
        (mockPrismaService.event as Record<string, unknown>).count = vi.fn().mockResolvedValue(0);

        await service.findAll({ workspaceId: WORKSPACE_A });

        expect(mockPrismaService.event.findMany).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              workspaceId: WORKSPACE_A,
            }),
          })
        );
      });

      it("should maintain workspaceId filter with date range filter", async () => {
        (mockPrismaService.event as Record<string, unknown>).findMany = vi
          .fn()
          .mockResolvedValue([]);
        (mockPrismaService.event as Record<string, unknown>).count = vi.fn().mockResolvedValue(0);

        const startFrom = new Date("2026-01-01");
        const startTo = new Date("2026-12-31");

        await service.findAll({
          workspaceId: WORKSPACE_A,
          startFrom,
          startTo,
        });

        const findManyCall = (mockPrismaService.event as Record<string, unknown>)
          .findMany as ReturnType<typeof vi.fn>;
        const whereClause = findManyCall.mock.calls[0][0].where;

        expect(whereClause.workspaceId).toBe(WORKSPACE_A);
        expect(whereClause.startTime).toBeDefined();
      });
    });

    describe("findOne() - workspaceId filtering", () => {
      it("should include workspaceId in findUnique query", async () => {
        const mockEvent = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          title: "Test",
        };
        (mockPrismaService.event as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockEvent);

        await service.findOne(ENTITY_ID, WORKSPACE_A);

        expect(mockPrismaService.event.findUnique).toHaveBeenCalledWith(
          expect.objectContaining({
            where: {
              id: ENTITY_ID,
              workspaceId: WORKSPACE_A,
            },
          })
        );
      });

      it("should NOT return event from different workspace", async () => {
        (mockPrismaService.event as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        await expect(service.findOne(ENTITY_ID, WORKSPACE_B)).rejects.toThrow(NotFoundException);
      });
    });

    describe("update() - workspaceId filtering", () => {
      it("should verify event belongs to workspace before update", async () => {
        const mockEvent = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          title: "Original",
          startTime: new Date(),
        };
        (mockPrismaService.event as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockEvent);
        (mockPrismaService.event as Record<string, unknown>).update = vi
          .fn()
          .mockResolvedValue({ ...mockEvent, title: "Updated" });

        await service.update(ENTITY_ID, WORKSPACE_A, USER_ID, { title: "Updated" });

        expect(mockPrismaService.event.update).toHaveBeenCalledWith(
          expect.objectContaining({
            where: {
              id: ENTITY_ID,
              workspaceId: WORKSPACE_A,
            },
          })
        );
      });

      it("should reject update for event in different workspace", async () => {
        (mockPrismaService.event as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        await expect(
          service.update(ENTITY_ID, WORKSPACE_B, USER_ID, { title: "Hacked" })
        ).rejects.toThrow(NotFoundException);

        expect(mockPrismaService.event.update).not.toHaveBeenCalled();
      });
    });

    describe("remove() - workspaceId filtering", () => {
      it("should verify event belongs to workspace before delete", async () => {
        const mockEvent = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          title: "To Delete",
        };
        (mockPrismaService.event as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockEvent);
        (mockPrismaService.event as Record<string, unknown>).delete = vi
          .fn()
          .mockResolvedValue(mockEvent);

        await service.remove(ENTITY_ID, WORKSPACE_A, USER_ID);

        expect(mockPrismaService.event.delete).toHaveBeenCalledWith({
          where: {
            id: ENTITY_ID,
            workspaceId: WORKSPACE_A,
          },
        });
      });
    });
  });

  /**
   * ============================================================================
   * KNOWLEDGE SERVICE - Workspace Isolation Tests
   * ============================================================================
   */
  describe("KnowledgeService - Workspace Isolation", () => {
    let service: KnowledgeService;
    let mockPrismaService: Record<string, unknown>;

    beforeEach(async () => {
      mockPrismaService = {
        knowledgeEntry: {
          create: vi.fn(),
          findMany: vi.fn(),
          count: vi.fn(),
          findUnique: vi.fn(),
          update: vi.fn(),
          delete: vi.fn(),
        },
        knowledgeEntryVersion: {
          create: vi.fn(),
          count: vi.fn(),
          findMany: vi.fn(),
          findUnique: vi.fn(),
        },
        knowledgeEntryTag: {
          deleteMany: vi.fn(),
          create: vi.fn(),
        },
        knowledgeTag: {
          findUnique: vi.fn(),
          create: vi.fn(),
        },
        $transaction: vi.fn((callback) => callback(mockPrismaService)),
      };

      const mockLinkSyncService = {
        syncLinks: vi.fn().mockResolvedValue(undefined),
      };

      const mockCacheService = {
        getEntry: vi.fn().mockResolvedValue(null),
        setEntry: vi.fn().mockResolvedValue(undefined),
        invalidateEntry: vi.fn().mockResolvedValue(undefined),
        invalidateSearches: vi.fn().mockResolvedValue(undefined),
        invalidateGraphs: vi.fn().mockResolvedValue(undefined),
        invalidateGraphsForEntry: vi.fn().mockResolvedValue(undefined),
      };

      const mockEmbeddingService = {
        isConfigured: vi.fn().mockReturnValue(false),
        prepareContentForEmbedding: vi.fn(
          (title: string, content: string) => `${title} ${content}`
        ),
        batchGenerateEmbeddings: vi.fn().mockResolvedValue(0),
      };

      const mockOllamaEmbeddingService = {
        isConfigured: vi.fn().mockResolvedValue(false),
        prepareContentForEmbedding: vi.fn(
          (title: string, content: string) => `${title} ${content}`
        ),
      };

      const mockEmbeddingQueueService = {
        queueEmbeddingJob: vi.fn().mockResolvedValue("job-123"),
      };

      const module: TestingModule = await Test.createTestingModule({
        providers: [
          KnowledgeService,
          { provide: PrismaService, useValue: mockPrismaService },
          { provide: LinkSyncService, useValue: mockLinkSyncService },
          { provide: KnowledgeCacheService, useValue: mockCacheService },
          { provide: EmbeddingService, useValue: mockEmbeddingService },
          { provide: OllamaEmbeddingService, useValue: mockOllamaEmbeddingService },
          { provide: EmbeddingQueueService, useValue: mockEmbeddingQueueService },
        ],
      }).compile();

      service = module.get<KnowledgeService>(KnowledgeService);
      vi.clearAllMocks();
    });

    describe("findAll() - workspaceId filtering", () => {
      it("should include workspaceId in where clause", async () => {
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).count = vi
          .fn()
          .mockResolvedValue(0);
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).findMany = vi
          .fn()
          .mockResolvedValue([]);

        await service.findAll(WORKSPACE_A, {});

        expect(mockPrismaService.knowledgeEntry.findMany).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              workspaceId: WORKSPACE_A,
            }),
          })
        );

        expect(mockPrismaService.knowledgeEntry.count).toHaveBeenCalledWith({
          where: expect.objectContaining({
            workspaceId: WORKSPACE_A,
          }),
        });
      });

      it("should maintain workspaceId filter with status filter", async () => {
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).count = vi
          .fn()
          .mockResolvedValue(0);
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).findMany = vi
          .fn()
          .mockResolvedValue([]);

        await service.findAll(WORKSPACE_A, { status: EntryStatus.PUBLISHED });

        const findManyCall = (mockPrismaService.knowledgeEntry as Record<string, unknown>)
          .findMany as ReturnType<typeof vi.fn>;
        const whereClause = findManyCall.mock.calls[0][0].where;

        expect(whereClause.workspaceId).toBe(WORKSPACE_A);
        expect(whereClause.status).toBe(EntryStatus.PUBLISHED);
      });
    });

    describe("findOne() - workspaceId filtering", () => {
      it("should use composite workspaceId_slug key", async () => {
        const mockEntry = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          slug: "test-entry",
          title: "Test",
          content: "Content",
          contentHtml: "<p>Content</p>",
          summary: null,
          status: EntryStatus.PUBLISHED,
          visibility: "WORKSPACE",
          createdAt: new Date(),
          updatedAt: new Date(),
          createdBy: USER_ID,
          updatedBy: USER_ID,
          tags: [],
        };
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockEntry);

        await service.findOne(WORKSPACE_A, "test-entry");

        expect(mockPrismaService.knowledgeEntry.findUnique).toHaveBeenCalledWith(
          expect.objectContaining({
            where: {
              workspaceId_slug: {
                workspaceId: WORKSPACE_A,
                slug: "test-entry",
              },
            },
          })
        );
      });

      it("should NOT return entry from different workspace", async () => {
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        await expect(service.findOne(WORKSPACE_B, "test-entry")).rejects.toThrow(NotFoundException);
      });
    });

    describe("create() - workspaceId binding", () => {
      it("should include workspaceId in create data", async () => {
        const mockEntry = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          slug: "new-entry",
          title: "New Entry",
          content: "Content",
          contentHtml: "<p>Content</p>",
          summary: null,
          status: EntryStatus.DRAFT,
          visibility: "PRIVATE",
          createdAt: new Date(),
          updatedAt: new Date(),
          createdBy: USER_ID,
          updatedBy: USER_ID,
          tags: [],
        };

        // Mock for ensureUniqueSlug check
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        // Mock for transaction
        (mockPrismaService.$transaction as ReturnType<typeof vi.fn>).mockImplementation(
          async (callback: (tx: Record<string, unknown>) => Promise<unknown>) => {
            const txMock = {
              knowledgeEntry: {
                create: vi.fn().mockResolvedValue(mockEntry),
                findUnique: vi.fn().mockResolvedValue(mockEntry),
              },
              knowledgeEntryVersion: {
                create: vi.fn().mockResolvedValue({}),
              },
              knowledgeEntryTag: {
                deleteMany: vi.fn(),
              },
              knowledgeTag: {
                findUnique: vi.fn(),
                create: vi.fn(),
              },
            };
            return callback(txMock);
          }
        );

        await service.create(WORKSPACE_A, USER_ID, {
          title: "New Entry",
          content: "Content",
        });

        // Verify transaction was called with workspaceId
        expect(mockPrismaService.$transaction).toHaveBeenCalled();
      });
    });

    describe("update() - workspaceId filtering", () => {
      it("should use composite workspaceId_slug key for update", async () => {
        const mockEntry = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          slug: "test-entry",
          title: "Test",
          content: "Content",
          contentHtml: "<p>Content</p>",
          summary: null,
          status: EntryStatus.PUBLISHED,
          visibility: "WORKSPACE",
          createdAt: new Date(),
          updatedAt: new Date(),
          createdBy: USER_ID,
          updatedBy: USER_ID,
          versions: [{ version: 1 }],
          tags: [],
        };

        (mockPrismaService.knowledgeEntry as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockEntry);

        (mockPrismaService.$transaction as ReturnType<typeof vi.fn>).mockImplementation(
          async (callback: (tx: Record<string, unknown>) => Promise<unknown>) => {
            const txMock = {
              knowledgeEntry: {
                update: vi.fn().mockResolvedValue(mockEntry),
                findUnique: vi.fn().mockResolvedValue(mockEntry),
              },
              knowledgeEntryVersion: {
                create: vi.fn().mockResolvedValue({}),
              },
              knowledgeEntryTag: {
                deleteMany: vi.fn(),
              },
              knowledgeTag: {
                findUnique: vi.fn(),
                create: vi.fn(),
              },
            };
            return callback(txMock);
          }
        );

        await service.update(WORKSPACE_A, "test-entry", USER_ID, { title: "Updated" });

        // Verify findUnique uses composite key
        expect(mockPrismaService.knowledgeEntry.findUnique).toHaveBeenCalledWith(
          expect.objectContaining({
            where: {
              workspaceId_slug: {
                workspaceId: WORKSPACE_A,
                slug: "test-entry",
              },
            },
          })
        );
      });

      it("should reject update for entry in different workspace", async () => {
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        await expect(
          service.update(WORKSPACE_B, "test-entry", USER_ID, { title: "Hacked" })
        ).rejects.toThrow(NotFoundException);
      });
    });

    describe("remove() - workspaceId filtering", () => {
      it("should use composite workspaceId_slug key for soft delete", async () => {
        const mockEntry = {
          id: ENTITY_ID,
          workspaceId: WORKSPACE_A,
          slug: "test-entry",
          title: "Test",
        };
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(mockEntry);
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).update = vi
          .fn()
          .mockResolvedValue({ ...mockEntry, status: EntryStatus.ARCHIVED });

        await service.remove(WORKSPACE_A, "test-entry", USER_ID);

        expect(mockPrismaService.knowledgeEntry.update).toHaveBeenCalledWith({
          where: {
            workspaceId_slug: {
              workspaceId: WORKSPACE_A,
              slug: "test-entry",
            },
          },
          data: {
            status: EntryStatus.ARCHIVED,
            updatedBy: USER_ID,
          },
        });
      });

      it("should reject remove for entry in different workspace", async () => {
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).findUnique = vi
          .fn()
          .mockResolvedValue(null);

        await expect(service.remove(WORKSPACE_B, "test-entry", USER_ID)).rejects.toThrow(
          NotFoundException
        );
      });
    });

    describe("batchGenerateEmbeddings() - workspaceId filtering", () => {
      it("should filter by workspaceId when generating embeddings", async () => {
        (mockPrismaService.knowledgeEntry as Record<string, unknown>).findMany = vi
          .fn()
          .mockResolvedValue([]);

        await service.batchGenerateEmbeddings(WORKSPACE_A);

        expect(mockPrismaService.knowledgeEntry.findMany).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              workspaceId: WORKSPACE_A,
            }),
          })
        );
      });
    });
  });

  /**
   * ============================================================================
   * CROSS-SERVICE SECURITY TESTS
   * ============================================================================
   */
  describe("Cross-Service Security Invariants", () => {
    it("should document that findAll without workspaceId is a security concern", () => {
      // This test documents the security finding:
      // TasksService.findAll, ProjectsService.findAll, and EventsService.findAll
      // accept empty query objects and will not filter by workspaceId.
      //
      // Recommendation: Make workspaceId a required parameter or throw an error
      // when workspaceId is not provided in multi-tenant context.
      //
      // KnowledgeService.findAll correctly requires workspaceId as first parameter.
      expect(true).toBe(true);
    });

    it("should verify all services use composite keys or compound where clauses", () => {
      // This test documents that all multi-tenant services should:
      // 1. Use workspaceId in where clauses for findMany/findFirst
      // 2. Use compound where clauses (id + workspaceId) for findUnique/update/delete
      // 3. Set workspaceId during create operations
      //
      // Current status:
      // - TasksService: Uses compound where (id, workspaceId) - GOOD
      // - ProjectsService: Uses compound where (id, workspaceId) - GOOD
      // - EventsService: Uses compound where (id, workspaceId) - GOOD
      // - KnowledgeService: Uses composite key (workspaceId_slug) - GOOD
      expect(true).toBe(true);
    });
  });
});
