import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { ActivityService } from "./activity.service";
import { PrismaService } from "../prisma/prisma.service";
import { ActivityAction, EntityType } from "@prisma/client";
import type { CreateActivityLogInput, QueryActivityLogDto } from "./interfaces/activity.interface";

describe("ActivityService", () => {
  let service: ActivityService;
  let prisma: PrismaService;

  const mockPrismaService = {
    activityLog: {
      create: vi.fn(),
      findMany: vi.fn(),
      findUnique: vi.fn(),
      count: vi.fn(),
    },
  };

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

    service = module.get<ActivityService>(ActivityService);
    prisma = module.get<PrismaService>(PrismaService);

    vi.clearAllMocks();
  });

  describe("logActivity", () => {
    const createInput: CreateActivityLogInput = {
      workspaceId: "workspace-123",
      userId: "user-123",
      action: ActivityAction.CREATED,
      entityType: EntityType.TASK,
      entityId: "task-123",
      details: { title: "New Task" },
      ipAddress: "127.0.0.1",
      userAgent: "Mozilla/5.0",
    };

    it("should create an activity log entry", async () => {
      const mockActivityLog = {
        id: "activity-123",
        ...createInput,
        createdAt: new Date(),
      };

      mockPrismaService.activityLog.create.mockResolvedValue(mockActivityLog);

      const result = await service.logActivity(createInput);

      expect(result).toEqual(mockActivityLog);
      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: createInput,
      });
    });

    it("should create activity log without optional fields", async () => {
      const minimalInput: CreateActivityLogInput = {
        workspaceId: "workspace-123",
        userId: "user-123",
        action: ActivityAction.UPDATED,
        entityType: EntityType.EVENT,
        entityId: "event-123",
      };

      const mockActivityLog = {
        id: "activity-456",
        ...minimalInput,
        details: {},
        createdAt: new Date(),
      };

      mockPrismaService.activityLog.create.mockResolvedValue(mockActivityLog);

      const result = await service.logActivity(minimalInput);

      expect(result).toEqual(mockActivityLog);
      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: minimalInput,
      });
    });
  });

  describe("findAll", () => {
    const mockActivities = [
      {
        id: "activity-1",
        workspaceId: "workspace-123",
        userId: "user-123",
        action: ActivityAction.CREATED,
        entityType: EntityType.TASK,
        entityId: "task-123",
        details: {},
        createdAt: new Date("2024-01-01"),
        user: {
          id: "user-123",
          name: "Test User",
          email: "test@example.com",
        },
      },
      {
        id: "activity-2",
        workspaceId: "workspace-123",
        userId: "user-123",
        action: ActivityAction.UPDATED,
        entityType: EntityType.TASK,
        entityId: "task-123",
        details: {},
        createdAt: new Date("2024-01-02"),
        user: {
          id: "user-123",
          name: "Test User",
          email: "test@example.com",
        },
      },
    ];

    it("should return paginated activity logs", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue(mockActivities);
      mockPrismaService.activityLog.count.mockResolvedValue(2);

      const result = await service.findAll(query);

      expect(result.data).toEqual(mockActivities);
      expect(result.meta).toEqual({
        total: 2,
        page: 1,
        limit: 10,
        totalPages: 1,
      });
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith({
        where: {
          workspaceId: "workspace-123",
        },
        include: {
          user: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
        orderBy: {
          createdAt: "desc",
        },
        skip: 0,
        take: 10,
      });
    });

    it("should filter by userId", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        userId: "user-123",
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([mockActivities[0]]);
      mockPrismaService.activityLog.count.mockResolvedValue(1);

      await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: "workspace-123",
            userId: "user-123",
          }),
        })
      );
    });

    it("should filter by action", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        action: ActivityAction.CREATED,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([mockActivities[0]]);
      mockPrismaService.activityLog.count.mockResolvedValue(1);

      await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: "workspace-123",
            action: ActivityAction.CREATED,
          }),
        })
      );
    });

    it("should filter by entityType and entityId", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        entityType: EntityType.TASK,
        entityId: "task-123",
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue(mockActivities);
      mockPrismaService.activityLog.count.mockResolvedValue(2);

      await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: "workspace-123",
            entityType: EntityType.TASK,
            entityId: "task-123",
          }),
        })
      );
    });

    it("should filter by date range", async () => {
      const startDate = new Date("2024-01-01");
      const endDate = new Date("2024-01-31");

      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        startDate,
        endDate,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue(mockActivities);
      mockPrismaService.activityLog.count.mockResolvedValue(2);

      await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: "workspace-123",
            createdAt: {
              gte: startDate,
              lte: endDate,
            },
          }),
        })
      );
    });

    it("should handle inverted date range (startDate > endDate)", async () => {
      const startDate = new Date("2024-12-31");
      const endDate = new Date("2024-01-01");

      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        startDate,
        endDate,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(0);

      await service.findAll(query);

      // Service should pass through inverted dates (let database handle it)
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            createdAt: {
              gte: startDate,
              lte: endDate,
            },
          }),
        })
      );
    });

    it("should handle dates in the future", async () => {
      const startDate = new Date("2030-01-01");
      const endDate = new Date("2030-12-31");

      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        startDate,
        endDate,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(0);

      const result = await service.findAll(query);

      expect(result.data).toEqual([]);
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            createdAt: {
              gte: startDate,
              lte: endDate,
            },
          }),
        })
      );
    });

    it("should handle very large date ranges", async () => {
      const startDate = new Date("1970-01-01");
      const endDate = new Date("2099-12-31");

      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        startDate,
        endDate,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue(mockActivities);
      mockPrismaService.activityLog.count.mockResolvedValue(2);

      const result = await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            createdAt: {
              gte: startDate,
              lte: endDate,
            },
          }),
        })
      );
    });

    it("should handle only startDate without endDate", async () => {
      const startDate = new Date("2024-01-01");

      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        startDate,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue(mockActivities);
      mockPrismaService.activityLog.count.mockResolvedValue(2);

      await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            createdAt: {
              gte: startDate,
            },
          }),
        })
      );
    });

    it("should handle only endDate without startDate", async () => {
      const endDate = new Date("2024-12-31");

      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        endDate,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue(mockActivities);
      mockPrismaService.activityLog.count.mockResolvedValue(2);

      await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            createdAt: {
              lte: endDate,
            },
          }),
        })
      );
    });

    it("should use default pagination values", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue(mockActivities);
      mockPrismaService.activityLog.count.mockResolvedValue(2);

      const result = await service.findAll(query);

      expect(result.meta.page).toBe(1);
      expect(result.meta.limit).toBe(50);
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          skip: 0,
          take: 50,
        })
      );
    });

    it("should calculate correct pagination", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        page: 2,
        limit: 25,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(100);

      const result = await service.findAll(query);

      expect(result.meta).toEqual({
        total: 100,
        page: 2,
        limit: 25,
        totalPages: 4,
      });
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          skip: 25,
          take: 25,
        })
      );
    });

    it("should handle page 0 as-is (nullish coalescing does not coerce 0 to 1)", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        page: 0,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(50);

      const result = await service.findAll(query);

      // Page 0 is kept as-is because ?? only defaults null/undefined
      expect(result.meta.page).toBe(0);
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          skip: -10, // (0 - 1) * 10 = -10
          take: 10,
        })
      );
    });

    it("should handle negative page numbers", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        page: -5,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(50);

      const result = await service.findAll(query);

      // Negative numbers are truthy, so -5 is used as-is
      expect(result.meta.page).toBe(-5);
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          skip: -60, // (-5 - 1) * 10 = -60
          take: 10,
        })
      );
    });

    it("should handle extremely large limit values", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        page: 1,
        limit: 10000,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(100);

      const result = await service.findAll(query);

      expect(result.meta.limit).toBe(10000);
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          skip: 0,
          take: 10000,
        })
      );
    });

    it("should handle page beyond total pages", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        page: 100,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(25);

      const result = await service.findAll(query);

      expect(result.meta).toEqual({
        total: 25,
        page: 100,
        limit: 10,
        totalPages: 3,
      });
      expect(result.data).toEqual([]);
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          skip: 990, // (100 - 1) * 10
          take: 10,
        })
      );
    });

    it("should handle empty result set with pagination", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-empty",
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(0);

      const result = await service.findAll(query);

      expect(result.meta).toEqual({
        total: 0,
        page: 1,
        limit: 10,
        totalPages: 0,
      });
      expect(result.data).toEqual([]);
    });
  });

  describe("findOne", () => {
    it("should return a single activity log by id", async () => {
      const mockActivity = {
        id: "activity-123",
        workspaceId: "workspace-123",
        userId: "user-123",
        action: ActivityAction.CREATED,
        entityType: EntityType.TASK,
        entityId: "task-123",
        details: {},
        createdAt: new Date(),
        user: {
          id: "user-123",
          name: "Test User",
          email: "test@example.com",
        },
      };

      mockPrismaService.activityLog.findUnique.mockResolvedValue(mockActivity);

      const result = await service.findOne("activity-123", "workspace-123");

      expect(result).toEqual(mockActivity);
      expect(mockPrismaService.activityLog.findUnique).toHaveBeenCalledWith({
        where: {
          id: "activity-123",
          workspaceId: "workspace-123",
        },
        include: {
          user: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
      });
    });

    it("should return null if activity log not found", async () => {
      mockPrismaService.activityLog.findUnique.mockResolvedValue(null);

      const result = await service.findOne("nonexistent", "workspace-123");

      expect(result).toBeNull();
    });
  });

  describe("getAuditTrail", () => {
    const mockAuditTrail = [
      {
        id: "activity-1",
        workspaceId: "workspace-123",
        userId: "user-123",
        action: ActivityAction.CREATED,
        entityType: EntityType.TASK,
        entityId: "task-123",
        details: { title: "New Task" },
        createdAt: new Date("2024-01-01"),
        user: {
          id: "user-123",
          name: "Test User",
          email: "test@example.com",
        },
      },
      {
        id: "activity-2",
        workspaceId: "workspace-123",
        userId: "user-456",
        action: ActivityAction.UPDATED,
        entityType: EntityType.TASK,
        entityId: "task-123",
        details: { title: "Updated Task" },
        createdAt: new Date("2024-01-02"),
        user: {
          id: "user-456",
          name: "Another User",
          email: "another@example.com",
        },
      },
    ];

    it("should return audit trail for an entity", async () => {
      mockPrismaService.activityLog.findMany.mockResolvedValue(mockAuditTrail);

      const result = await service.getAuditTrail("workspace-123", EntityType.TASK, "task-123");

      expect(result).toEqual(mockAuditTrail);
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith({
        where: {
          workspaceId: "workspace-123",
          entityType: EntityType.TASK,
          entityId: "task-123",
        },
        include: {
          user: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
        orderBy: {
          createdAt: "asc",
        },
      });
    });

    it("should return empty array if no audit trail found", async () => {
      mockPrismaService.activityLog.findMany.mockResolvedValue([]);

      const result = await service.getAuditTrail(
        "workspace-123",
        EntityType.PROJECT,
        "project-999"
      );

      expect(result).toEqual([]);
    });
  });

  describe("negative validation", () => {
    it("should handle invalid UUID formats gracefully", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "not-a-uuid",
        userId: "also-not-uuid",
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(0);

      // Service should pass through to Prisma, which may reject it
      const result = await service.findAll(query);
      expect(result.data).toEqual([]);
    });

    it("should handle invalid enum values by passing through", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        action: "INVALID_ACTION" as ActivityAction,
        entityType: "INVALID_TYPE" as EntityType,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(0);

      const result = await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            action: "INVALID_ACTION",
            entityType: "INVALID_TYPE",
          }),
        })
      );
    });

    it("should handle extremely long strings", async () => {
      const longString = "a".repeat(10000);
      const query: QueryActivityLogDto = {
        workspaceId: longString,
        entityId: longString,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(0);

      await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: longString,
            entityId: longString,
          }),
        })
      );
    });

    it("should handle SQL injection attempts in string fields", async () => {
      const maliciousInput = "'; DROP TABLE activityLog; --";
      const query: QueryActivityLogDto = {
        workspaceId: maliciousInput,
        entityId: maliciousInput,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(0);

      // Prisma should sanitize this, service just passes through
      await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: maliciousInput,
          }),
        })
      );
    });

    it("should handle special characters in filters", async () => {
      const specialChars = "!@#$%^&*(){}[]|\\:;\"'<>?/~`";
      const query: QueryActivityLogDto = {
        workspaceId: specialChars,
        entityId: specialChars,
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(0);

      await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: specialChars,
          }),
        })
      );
    });

    it("should handle database errors gracefully when logging activity (fire-and-forget)", async () => {
      const input: CreateActivityLogInput = {
        workspaceId: "workspace-123",
        userId: "user-123",
        action: ActivityAction.CREATED,
        entityType: EntityType.TASK,
        entityId: "task-123",
      };

      const dbError = new Error("Database connection failed");
      mockPrismaService.activityLog.create.mockRejectedValue(dbError);

      // Activity logging is fire-and-forget - returns null on error instead of throwing
      const result = await service.logActivity(input);
      expect(result).toBeNull();
    });

    it("should handle extremely large details objects", async () => {
      const hugeDetails = {
        data: "x".repeat(100000),
        nested: {
          level1: {
            level2: {
              level3: {
                data: "y".repeat(50000),
              },
            },
          },
        },
      };

      const input: CreateActivityLogInput = {
        workspaceId: "workspace-123",
        userId: "user-123",
        action: ActivityAction.CREATED,
        entityType: EntityType.TASK,
        entityId: "task-123",
        details: hugeDetails,
      };

      mockPrismaService.activityLog.create.mockResolvedValue({
        id: "activity-123",
        ...input,
        createdAt: new Date(),
      });

      const result = await service.logActivity(input);
      expect(result.details).toEqual(hugeDetails);
    });
  });

  describe("helper methods", () => {
    const mockActivityLog = {
      id: "activity-123",
      workspaceId: "workspace-123",
      userId: "user-123",
      action: ActivityAction.CREATED,
      entityType: EntityType.TASK,
      entityId: "task-123",
      details: {},
      createdAt: new Date(),
    };

    beforeEach(() => {
      mockPrismaService.activityLog.create.mockResolvedValue(mockActivityLog);
    });

    it("should log task creation with details", async () => {
      const result = await service.logTaskCreated("workspace-123", "user-123", "task-123", {
        title: "New Task",
      });

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.CREATED,
          entityType: EntityType.TASK,
          entityId: "task-123",
          details: { title: "New Task" },
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log task update with changes", async () => {
      const result = await service.logTaskUpdated("workspace-123", "user-123", "task-123", {
        changes: { status: "IN_PROGRESS" },
      });

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.UPDATED,
          entityType: EntityType.TASK,
          entityId: "task-123",
          details: { changes: { status: "IN_PROGRESS" } },
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log task deletion without details", async () => {
      const result = await service.logTaskDeleted("workspace-123", "user-123", "task-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.DELETED,
          entityType: EntityType.TASK,
          entityId: "task-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log task completion", async () => {
      const result = await service.logTaskCompleted("workspace-123", "user-123", "task-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.COMPLETED,
          entityType: EntityType.TASK,
          entityId: "task-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log task assignment with assignee details", async () => {
      const result = await service.logTaskAssigned(
        "workspace-123",
        "user-123",
        "task-123",
        "user-456"
      );

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.ASSIGNED,
          entityType: EntityType.TASK,
          entityId: "task-123",
          details: { assigneeId: "user-456" },
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log event creation", async () => {
      const result = await service.logEventCreated("workspace-123", "user-123", "event-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.CREATED,
          entityType: EntityType.EVENT,
          entityId: "event-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log event update", async () => {
      const result = await service.logEventUpdated("workspace-123", "user-123", "event-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.UPDATED,
          entityType: EntityType.EVENT,
          entityId: "event-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log event deletion", async () => {
      const result = await service.logEventDeleted("workspace-123", "user-123", "event-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.DELETED,
          entityType: EntityType.EVENT,
          entityId: "event-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log project creation", async () => {
      const result = await service.logProjectCreated("workspace-123", "user-123", "project-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.CREATED,
          entityType: EntityType.PROJECT,
          entityId: "project-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log project update", async () => {
      const result = await service.logProjectUpdated("workspace-123", "user-123", "project-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.UPDATED,
          entityType: EntityType.PROJECT,
          entityId: "project-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log project deletion", async () => {
      const result = await service.logProjectDeleted("workspace-123", "user-123", "project-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.DELETED,
          entityType: EntityType.PROJECT,
          entityId: "project-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log workspace creation", async () => {
      const result = await service.logWorkspaceCreated("workspace-123", "user-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.CREATED,
          entityType: EntityType.WORKSPACE,
          entityId: "workspace-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log workspace update", async () => {
      const result = await service.logWorkspaceUpdated("workspace-123", "user-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.UPDATED,
          entityType: EntityType.WORKSPACE,
          entityId: "workspace-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log workspace member addition with role", async () => {
      const result = await service.logWorkspaceMemberAdded(
        "workspace-123",
        "user-123",
        "user-456",
        "MEMBER"
      );

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.CREATED,
          entityType: EntityType.WORKSPACE,
          entityId: "workspace-123",
          details: { memberId: "user-456", role: "MEMBER" },
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log workspace member removal with member ID", async () => {
      const result = await service.logWorkspaceMemberRemoved(
        "workspace-123",
        "user-123",
        "user-456"
      );

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.DELETED,
          entityType: EntityType.WORKSPACE,
          entityId: "workspace-123",
          details: { memberId: "user-456" },
        },
      });
      expect(result).toEqual(mockActivityLog);
    });

    it("should log user update", async () => {
      const result = await service.logUserUpdated("workspace-123", "user-123");

      expect(mockPrismaService.activityLog.create).toHaveBeenCalledWith({
        data: {
          workspaceId: "workspace-123",
          userId: "user-123",
          action: ActivityAction.UPDATED,
          entityType: EntityType.USER,
          entityId: "user-123",
        },
      });
      expect(result).toEqual(mockActivityLog);
    });
  });

  describe("database error handling", () => {
    it("should handle database connection failures in logActivity (fire-and-forget)", async () => {
      const createInput: CreateActivityLogInput = {
        workspaceId: "workspace-123",
        userId: "user-123",
        action: ActivityAction.CREATED,
        entityType: EntityType.TASK,
        entityId: "task-123",
      };

      const dbError = new Error("Connection refused");
      mockPrismaService.activityLog.create.mockRejectedValue(dbError);

      // Activity logging is fire-and-forget - returns null on error instead of throwing
      const result = await service.logActivity(createInput);
      expect(result).toBeNull();
    });

    it("should handle Prisma timeout errors in findAll", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
      };

      const timeoutError = new Error("Query timeout");
      mockPrismaService.activityLog.findMany.mockRejectedValue(timeoutError);

      await expect(service.findAll(query)).rejects.toThrow("Query timeout");
    });

    it("should handle Prisma errors in findOne", async () => {
      const dbError = new Error("Record not found");
      mockPrismaService.activityLog.findUnique.mockRejectedValue(dbError);

      await expect(service.findOne("activity-123", "workspace-123")).rejects.toThrow(
        "Record not found"
      );
    });

    it("should handle malformed query parameters in findAll", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        startDate: new Date("invalid-date"),
      };

      mockPrismaService.activityLog.findMany.mockRejectedValue(new Error("Invalid date format"));

      await expect(service.findAll(query)).rejects.toThrow("Invalid date format");
    });

    it("should handle database errors in getAuditTrail", async () => {
      const dbError = new Error("Database connection lost");
      mockPrismaService.activityLog.findMany.mockRejectedValue(dbError);

      await expect(
        service.getAuditTrail("workspace-123", EntityType.TASK, "task-123")
      ).rejects.toThrow("Database connection lost");
    });

    it("should handle count query failures in findAll", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-123",
        page: 1,
        limit: 10,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockRejectedValue(new Error("Count query failed"));

      await expect(service.findAll(query)).rejects.toThrow("Count query failed");
    });
  });

  describe("multi-tenant isolation", () => {
    it("should prevent cross-workspace data leakage in findAll", async () => {
      const workspace1Query: QueryActivityLogDto = {
        workspaceId: "workspace-111",
        page: 1,
        limit: 10,
      };

      const workspace1Activities = [
        {
          id: "activity-1",
          workspaceId: "workspace-111",
          userId: "user-123",
          action: ActivityAction.CREATED,
          entityType: EntityType.TASK,
          entityId: "task-123",
          details: {},
          createdAt: new Date(),
          user: {
            id: "user-123",
            name: "User 1",
            email: "user1@example.com",
          },
        },
      ];

      mockPrismaService.activityLog.findMany.mockResolvedValue(workspace1Activities);
      mockPrismaService.activityLog.count.mockResolvedValue(1);

      const result = await service.findAll(workspace1Query);

      expect(result.data).toHaveLength(1);
      expect(result.data[0].workspaceId).toBe("workspace-111");
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: "workspace-111",
          }),
        })
      );
    });

    it("should enforce workspace filtering in findOne", async () => {
      const activityId = "activity-shared-123";
      const workspaceId = "workspace-222";

      mockPrismaService.activityLog.findUnique.mockResolvedValue(null);

      const result = await service.findOne(activityId, workspaceId);

      expect(result).toBeNull();
      expect(mockPrismaService.activityLog.findUnique).toHaveBeenCalledWith({
        where: {
          id: activityId,
          workspaceId: workspaceId,
        },
        include: {
          user: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
      });
    });

    it("should isolate audit trails by workspace", async () => {
      const workspaceId = "workspace-333";
      const entityType = EntityType.TASK;
      const entityId = "task-shared-456";

      const workspace3Activities = [
        {
          id: "activity-1",
          workspaceId: "workspace-333",
          userId: "user-789",
          action: ActivityAction.CREATED,
          entityType: EntityType.TASK,
          entityId: "task-shared-456",
          details: {},
          createdAt: new Date(),
          user: {
            id: "user-789",
            name: "User 3",
            email: "user3@example.com",
          },
        },
      ];

      mockPrismaService.activityLog.findMany.mockResolvedValue(workspace3Activities);

      const result = await service.getAuditTrail(workspaceId, entityType, entityId);

      expect(result).toHaveLength(1);
      expect(result[0].workspaceId).toBe("workspace-333");
      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: "workspace-333",
            entityType: EntityType.TASK,
            entityId: "task-shared-456",
          }),
        })
      );
    });

    it("should verify workspace filtering with multiple filters in findAll", async () => {
      const query: QueryActivityLogDto = {
        workspaceId: "workspace-444",
        userId: "user-999",
        action: ActivityAction.UPDATED,
        entityType: EntityType.PROJECT,
      };

      mockPrismaService.activityLog.findMany.mockResolvedValue([]);
      mockPrismaService.activityLog.count.mockResolvedValue(0);

      await service.findAll(query);

      expect(mockPrismaService.activityLog.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: "workspace-444",
            userId: "user-999",
            action: ActivityAction.UPDATED,
            entityType: EntityType.PROJECT,
          }),
        })
      );
    });

    it("should handle user with multiple workspaces correctly", async () => {
      const userId = "multi-workspace-user";
      const workspace1Query: QueryActivityLogDto = {
        workspaceId: "workspace-aaa",
        userId,
      };
      const workspace2Query: QueryActivityLogDto = {
        workspaceId: "workspace-bbb",
        userId,
      };

      const workspace1Activities = [
        {
          id: "activity-w1",
          workspaceId: "workspace-aaa",
          userId,
          action: ActivityAction.CREATED,
          entityType: EntityType.TASK,
          entityId: "task-w1",
          details: {},
          createdAt: new Date(),
          user: { id: userId, name: "Multi User", email: "multi@example.com" },
        },
      ];

      const workspace2Activities = [
        {
          id: "activity-w2",
          workspaceId: "workspace-bbb",
          userId,
          action: ActivityAction.CREATED,
          entityType: EntityType.EVENT,
          entityId: "event-w2",
          details: {},
          createdAt: new Date(),
          user: { id: userId, name: "Multi User", email: "multi@example.com" },
        },
      ];

      mockPrismaService.activityLog.findMany.mockResolvedValueOnce(workspace1Activities);
      mockPrismaService.activityLog.count.mockResolvedValueOnce(1);

      const result1 = await service.findAll(workspace1Query);

      expect(result1.data).toHaveLength(1);
      expect(result1.data[0].workspaceId).toBe("workspace-aaa");

      mockPrismaService.activityLog.findMany.mockResolvedValueOnce(workspace2Activities);
      mockPrismaService.activityLog.count.mockResolvedValueOnce(1);

      const result2 = await service.findAll(workspace2Query);

      expect(result2.data).toHaveLength(1);
      expect(result2.data[0].workspaceId).toBe("workspace-bbb");
    });
  });
});
