import { BadRequestException, ForbiddenException } from '@nestjs/common';
import { Types } from 'mongoose';
import { CollaborationRequestsService } from './collaboration-requests.service';

const chain = <T>(value: T) => ({
  populate: jest.fn().mockReturnThis(),
  sort: jest.fn().mockReturnThis(),
  skip: jest.fn().mockReturnThis(),
  limit: jest.fn().mockReturnThis(),
  exec: jest.fn().mockResolvedValue(value),
});

const createUser = (overrides: Record<string, any> = {}) => {
  const userId = overrides._id ?? new Types.ObjectId();
  return {
    _id: userId,
    id: userId.toString(),
    username: overrides.username ?? 'artist',
    name: overrides.name ?? 'Artist Name',
    stageName: overrides.stageName ?? 'Stage Artist',
    avatar: overrides.avatar ?? '/avatar.png',
    isVerified: overrides.isVerified ?? false,
    ...overrides,
  };
};

const createPost = (overrides: Record<string, any> = {}) => {
  const postId = overrides._id ?? new Types.ObjectId();
  return {
    _id: postId,
    id: postId.toString(),
    content: overrides.content ?? 'Post content',
    thumbnailUrl: overrides.thumbnailUrl ?? '/thumb.png',
    media: overrides.media ?? {},
    authorId: overrides.authorId ?? new Types.ObjectId(),
    ...overrides,
  };
};

const createRequestDoc = (overrides: Record<string, any> = {}) => {
  const requestId = overrides._id ?? new Types.ObjectId();
  return {
    _id: requestId,
    id: requestId.toString(),
    postId: Object.prototype.hasOwnProperty.call(overrides, 'postId')
      ? overrides.postId
      : new Types.ObjectId(),
    requesterId: overrides.requesterId ?? new Types.ObjectId(),
    targetUserId: overrides.targetUserId ?? new Types.ObjectId(),
    status: overrides.status ?? 'pending',
    collaborationType: overrides.collaborationType ?? 'duet',
    save: jest.fn().mockResolvedValue(undefined),
    ...overrides,
  };
};

const createService = () => {
  const model = {
    findOne: jest.fn(),
    findOneAndUpdate: jest.fn(),
    findById: jest.fn(),
    findByIdAndUpdate: jest.fn(),
    find: jest.fn(),
    countDocuments: jest.fn(),
  };
  const postsRepository = {
    findById: jest.fn(),
    updateById: jest.fn(),
  };
  const usersRepository = {
    findById: jest.fn(),
  };
  const blocksRepository = {
    findAnyBetween: jest.fn(),
  };
  const notificationsService = {
    create: jest.fn(),
  };
  const storageService = {
    saveFile: jest.fn().mockResolvedValue('/uploads/collaboration/audio/collaboration-test.mp3'),
  };

  const service = new CollaborationRequestsService(
    model as any,
    postsRepository as any,
    usersRepository as any,
    blocksRepository as any,
    notificationsService as any,
    storageService as any,
  );

  return {
    service,
    model,
    postsRepository,
    usersRepository,
    blocksRepository,
    notificationsService,
    storageService,
  };
};

describe('CollaborationRequestsService', () => {
  it('allows a non-owner viewer to request collaboration on a post and notifies the post owner', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const postId = new Types.ObjectId().toString();
    const request = createRequestDoc({
      postId: new Types.ObjectId(postId),
      requesterId: new Types.ObjectId(requesterId),
      targetUserId: new Types.ObjectId(targetUserId),
    });
    const { service, model, postsRepository, usersRepository, blocksRepository, notificationsService } =
      createService();

    postsRepository.findById.mockResolvedValue({ authorId: new Types.ObjectId(targetUserId) });
    usersRepository.findById.mockResolvedValue({ isDisabled: false });
    blocksRepository.findAnyBetween.mockResolvedValue(null);
    model.findOne
      .mockReturnValueOnce(chain(null))
      .mockReturnValueOnce(chain({ ...request, populated: true }));
    model.findOneAndUpdate.mockReturnValue(chain(request));

    await expect(
      service.create(requesterId, postId, {
        collaborationType: 'duet',
        message: 'Let us work',
      }),
    ).resolves.toMatchObject({
      message: 'Collaboration request sent',
      request: expect.objectContaining({ populated: true }),
    });

    expect(model.findOneAndUpdate).toHaveBeenCalledWith(
      expect.objectContaining({
        postId: new Types.ObjectId(postId),
        requesterId: new Types.ObjectId(requesterId),
        targetUserId: new Types.ObjectId(targetUserId),
        status: 'pending',
      }),
      expect.any(Object),
      expect.any(Object),
    );
    expect(notificationsService.create).toHaveBeenCalledWith(
      expect.objectContaining({
        actorId: requesterId,
        recipientId: targetUserId,
        type: 'collaboration_request',
        referenceId: postId,
        deepLink: `/posts/${postId}`,
        metadata: expect.objectContaining({
          collaborationRequestId: request.id,
          requestId: request.id,
          collaborationType: 'duet',
          status: 'pending',
          postId,
        }),
      }),
    );
  });

  it('uses populated post author id as target user and ignores empty attachmentUrl strings', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const postId = new Types.ObjectId().toString();
    const request = createRequestDoc({
      postId: new Types.ObjectId(postId),
      requesterId: new Types.ObjectId(requesterId),
      targetUserId: new Types.ObjectId(targetUserId),
      attachmentUrl: '',
      attachmentType: 'audio',
    });
    const { service, model, postsRepository, usersRepository, blocksRepository } = createService();

    postsRepository.findById.mockResolvedValue({
      authorId: { _id: new Types.ObjectId(targetUserId), username: 'owner' },
    });
    usersRepository.findById.mockResolvedValue({ isDisabled: false });
    blocksRepository.findAnyBetween.mockResolvedValue(null);
    model.findOne
      .mockReturnValueOnce(chain(null))
      .mockReturnValueOnce(chain({ ...request, populated: true }));
    model.findOneAndUpdate.mockReturnValue(chain(request));

    await expect(
      service.create(requesterId, postId, {
        collaborationType: 'duet',
        message: 'Let us work',
        attachmentType: 'audio',
        attachmentUrl: '',
      }),
    ).resolves.toMatchObject({
      message: 'Collaboration request sent',
    });

    expect(usersRepository.findById).toHaveBeenCalledWith(targetUserId);
    expect(model.findOneAndUpdate).toHaveBeenCalledWith(
      expect.objectContaining({
        targetUserId: new Types.ObjectId(targetUserId),
      }),
      expect.objectContaining({
        $setOnInsert: expect.not.objectContaining({
          attachmentUrl: '',
        }),
      }),
      expect.any(Object),
    );
  });

  it('prevents the post owner from requesting collaboration on their own post', async () => {
    const requesterId = new Types.ObjectId().toString();
    const postId = new Types.ObjectId().toString();
    const { service, postsRepository } = createService();

    postsRepository.findById.mockResolvedValue({ authorId: new Types.ObjectId(requesterId) });

    await expect(service.create(requesterId, postId, {})).rejects.toThrow(
      'You cannot request collaboration on your own post',
    );
  });

  it('creates a general collaboration request from a user profile', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const request = createRequestDoc({
      postId: null,
      requesterId: new Types.ObjectId(requesterId),
      targetUserId: new Types.ObjectId(targetUserId),
    });
    const { service, model, usersRepository, blocksRepository, notificationsService } =
      createService();

    usersRepository.findById.mockResolvedValue({ isDisabled: false });
    blocksRepository.findAnyBetween.mockResolvedValue(null);
    model.findOne
      .mockReturnValueOnce(chain(null))
      .mockReturnValueOnce(chain({ ...request, populated: true }));
    model.findOneAndUpdate.mockReturnValue(chain(request));

    await expect(
      service.createGeneral(requesterId, targetUserId, {
        collaborationType: 'duet',
        message: 'Let us work without a post',
        attachmentType: 'audio',
      }),
    ).resolves.toMatchObject({
      message: 'Collaboration request sent',
      request: expect.objectContaining({ populated: true }),
    });

    expect(model.findOneAndUpdate).toHaveBeenCalledWith(
      expect.objectContaining({
        postId: null,
        requesterId: new Types.ObjectId(requesterId),
        targetUserId: new Types.ObjectId(targetUserId),
        status: 'pending',
      }),
      expect.objectContaining({
        $setOnInsert: expect.objectContaining({ postId: null }),
      }),
      expect.any(Object),
    );
    expect(notificationsService.create).toHaveBeenCalledWith(
      expect.objectContaining({
        actorId: requesterId,
        recipientId: targetUserId,
        type: 'collaboration_request',
        resourceType: 'user',
        referenceId: requesterId,
        deepLink: `/users/${requesterId}`,
      }),
    );
  });

  it('updates duplicate pending general requests instead of creating duplicates', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const existing = createRequestDoc({
      postId: null,
      requesterId: new Types.ObjectId(requesterId),
      targetUserId: new Types.ObjectId(targetUserId),
    });
    const updated = { ...existing, message: 'Updated profile collaboration' };
    const { service, model, usersRepository, blocksRepository, notificationsService } =
      createService();

    usersRepository.findById.mockResolvedValue({ isDisabled: false });
    blocksRepository.findAnyBetween.mockResolvedValue(null);
    model.findOne
      .mockReturnValueOnce(chain(existing))
      .mockReturnValueOnce(chain({ ...updated, populated: true }));
    model.findByIdAndUpdate.mockReturnValue(chain(updated));

    await service.createGeneral(requesterId, targetUserId, {
      message: 'Updated profile collaboration',
    });

    expect(model.findByIdAndUpdate).toHaveBeenCalledWith(
      existing.id,
      { $set: { message: 'Updated profile collaboration' } },
      { new: true },
    );
    expect(model.findOneAndUpdate).not.toHaveBeenCalled();
    expect(notificationsService.create).not.toHaveBeenCalled();
  });

  it('prevents self general collaboration requests', async () => {
    const requesterId = new Types.ObjectId().toString();
    const { service } = createService();

    await expect(service.createGeneral(requesterId, requesterId, {})).rejects.toBeInstanceOf(
      BadRequestException,
    );
  });

  it('prevents general requests to disabled target users', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const { service, usersRepository, blocksRepository } = createService();

    usersRepository.findById.mockResolvedValue({ isDisabled: true });
    blocksRepository.findAnyBetween.mockResolvedValue(null);

    await expect(service.createGeneral(requesterId, targetUserId, {})).rejects.toThrow(
      'Target user not found',
    );
  });

  it('respects blocks for general collaboration requests', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const { service, usersRepository, blocksRepository } = createService();

    usersRepository.findById.mockResolvedValue({ isDisabled: false });
    blocksRepository.findAnyBetween.mockResolvedValue({ _id: new Types.ObjectId() });

    await expect(service.createGeneral(requesterId, targetUserId, {})).rejects.toBeInstanceOf(
      BadRequestException,
    );
  });

  it('lists received requests with optional status filter', async () => {
    const targetUserId = new Types.ObjectId().toString();
    const items = [createRequestDoc({ targetUserId: new Types.ObjectId(targetUserId) })];
    const { service, model } = createService();

    model.find.mockReturnValue(chain(items));
    model.countDocuments.mockReturnValue(chain(1));

    const result = await service.listReceived(targetUserId, {
      page: 1,
      limit: 20,
      status: 'approved',
    });

    expect(result.items).toHaveLength(1);
    expect(model.find).toHaveBeenCalledWith({
      targetUserId: new Types.ObjectId(targetUserId),
      status: 'approved',
    });
  });

  it('returns request details with requester, targetUser, and post aliases', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const postId = new Types.ObjectId().toString();
    const requester = createUser({ _id: new Types.ObjectId(requesterId), username: 'rami' });
    const targetUser = createUser({ _id: new Types.ObjectId(targetUserId), username: 'sara' });
    const post = createPost({ _id: new Types.ObjectId(postId), authorId: targetUser._id });
    const request = createRequestDoc({
      postId: post,
      requesterId: requester,
      targetUserId: targetUser,
      attachmentUrl: '/uploads/collaboration-requests/audio/file.mp3',
      attachmentType: 'audio',
    });
    const { service, model } = createService();

    model.findOne.mockReturnValue(chain(request));

    const result = await service.getById(requesterId, request.id);

    expect(result.request).toMatchObject({
      id: request.id,
      postId,
      attachmentUrl: '/uploads/collaboration-requests/audio/file.mp3',
      attachmentType: 'audio',
      requester: expect.objectContaining({ id: requesterId, username: 'rami' }),
      targetUser: expect.objectContaining({ id: targetUserId, username: 'sara' }),
      post: expect.objectContaining({ id: postId, content: 'Post content' }),
    });
  });

  it('lists received requests with populated requester and targetUser aliases', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const items = [
      createRequestDoc({
        requesterId: createUser({ _id: new Types.ObjectId(requesterId), username: 'rami' }),
        targetUserId: createUser({ _id: new Types.ObjectId(targetUserId), username: 'sara' }),
      }),
    ];
    const { service, model } = createService();

    model.find.mockReturnValue(chain(items));
    model.countDocuments.mockReturnValue(chain(1));

    const result = await service.listReceived(targetUserId, { page: 1, limit: 20 });

    expect(result.items[0]).toMatchObject({
      requester: expect.objectContaining({ id: requesterId, username: 'rami' }),
      targetUser: expect.objectContaining({ id: targetUserId, username: 'sara' }),
    });
  });
  it('approves a pending request, updates post collaborators, and notifies requester', async () => {
    const targetUserId = new Types.ObjectId().toString();
    const requesterId = new Types.ObjectId().toString();
    const postId = new Types.ObjectId();
    const request = createRequestDoc({
      postId,
      requesterId: new Types.ObjectId(requesterId),
      targetUserId: new Types.ObjectId(targetUserId),
    });
    const { service, model, postsRepository, notificationsService } = createService();

    model.findById.mockReturnValue(chain(request));
    model.findOne.mockReturnValue(chain({ ...request, status: 'approved' }));

    await expect(service.approve(targetUserId, request.id)).resolves.toMatchObject({
      approved: true,
      request: expect.objectContaining({ status: 'approved' }),
    });

    expect(postsRepository.updateById).toHaveBeenCalledWith(postId.toString(), {
      $addToSet: { collaboratorIds: request.requesterId },
    });
    expect(notificationsService.create).toHaveBeenCalledWith(
      expect.objectContaining({
        recipientId: requesterId,
        type: 'collaboration_request_approved',
      }),
    );
  });

  it('approves a general request without updating post collaborators', async () => {
    const targetUserId = new Types.ObjectId().toString();
    const requesterId = new Types.ObjectId().toString();
    const request = createRequestDoc({
      postId: null,
      requesterId: new Types.ObjectId(requesterId),
      targetUserId: new Types.ObjectId(targetUserId),
    });
    const { service, model, postsRepository, notificationsService } = createService();

    model.findById.mockReturnValue(chain(request));
    model.findOne.mockReturnValue(chain({ ...request, status: 'approved' }));

    await expect(service.approve(targetUserId, request.id)).resolves.toMatchObject({
      approved: true,
      request: expect.objectContaining({ status: 'approved' }),
    });

    expect(postsRepository.updateById).not.toHaveBeenCalled();
    expect(notificationsService.create).toHaveBeenCalledWith(
      expect.objectContaining({
        recipientId: requesterId,
        type: 'collaboration_request_approved',
        resourceType: 'user',
        referenceId: targetUserId,
        deepLink: `/users/${targetUserId}`,
      }),
    );
  });

  it('allows only the requester to cancel a pending request', async () => {
    const requesterId = new Types.ObjectId().toString();
    const otherUserId = new Types.ObjectId().toString();
    const request = createRequestDoc({ requesterId: new Types.ObjectId(requesterId) });
    const { service, model } = createService();

    model.findById.mockReturnValue(chain(request));

    await expect(service.cancel(otherUserId, request.id)).rejects.toBeInstanceOf(ForbiddenException);
    expect(request.save).not.toHaveBeenCalled();
  });

  it('rejects cancelling non-pending requests', async () => {
    const requesterId = new Types.ObjectId().toString();
    const request = createRequestDoc({
      requesterId: new Types.ObjectId(requesterId),
      status: 'approved',
    });
    const { service, model } = createService();

    model.findById.mockReturnValue(chain(request));

    await expect(service.cancel(requesterId, request.id)).rejects.toBeInstanceOf(BadRequestException);
    expect(request.save).not.toHaveBeenCalled();
  });

  it('validates post, owner, target, and block state for post collaboration requests', async () => {
    const requesterId = new Types.ObjectId().toString();
    const postId = new Types.ObjectId().toString();

    const invalid = createService();
    await expect(invalid.service.create(requesterId, 'invalid', {})).rejects.toThrow(
      'Invalid collaboration request',
    );

    const missingPost = createService();
    missingPost.postsRepository.findById.mockResolvedValue(null);
    await expect(missingPost.service.create(requesterId, postId, {})).rejects.toThrow(
      'Post not found',
    );

    const invalidOwner = createService();
    invalidOwner.postsRepository.findById.mockResolvedValue({ authorId: {} });
    await expect(invalidOwner.service.create(requesterId, postId, {})).rejects.toThrow(
      'Post owner is invalid',
    );

    const targetId = new Types.ObjectId().toString();
    const disabled = createService();
    disabled.postsRepository.findById.mockResolvedValue({ authorId: targetId });
    disabled.usersRepository.findById.mockResolvedValue({ isDisabled: true });
    disabled.blocksRepository.findAnyBetween.mockResolvedValue(null);
    await expect(disabled.service.create(requesterId, postId, {})).rejects.toThrow(
      'Target user not found',
    );

    const blocked = createService();
    blocked.postsRepository.findById.mockResolvedValue({ authorId: targetId });
    blocked.usersRepository.findById.mockResolvedValue({ isDisabled: false });
    blocked.blocksRepository.findAnyBetween.mockResolvedValue({ id: 'block-1' });
    await expect(blocked.service.create(requesterId, postId, {})).rejects.toThrow(
      'You cannot invite this user',
    );
  });

  it('reports failed upserts for post and general requests', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetId = new Types.ObjectId().toString();
    const postId = new Types.ObjectId().toString();

    const postRequest = createService();
    postRequest.postsRepository.findById.mockResolvedValue({ authorId: targetId });
    postRequest.usersRepository.findById.mockResolvedValue({ isDisabled: false });
    postRequest.blocksRepository.findAnyBetween.mockResolvedValue(null);
    postRequest.model.findOne.mockReturnValue(chain(null));
    postRequest.model.findOneAndUpdate.mockReturnValue(chain(null));
    await expect(postRequest.service.create(requesterId, postId, {})).rejects.toThrow(
      'Collaboration request not found',
    );

    const generalRequest = createService();
    generalRequest.usersRepository.findById.mockResolvedValue({ isDisabled: false });
    generalRequest.blocksRepository.findAnyBetween.mockResolvedValue(null);
    generalRequest.model.findOne.mockReturnValue(chain(null));
    generalRequest.model.findOneAndUpdate.mockReturnValue(chain(null));
    await expect(
      generalRequest.service.createGeneral(requesterId, targetId, {}),
    ).rejects.toThrow('Collaboration request not found');

    await expect(
      generalRequest.service.createGeneral(requesterId, 'invalid', {}),
    ).rejects.toThrow('Invalid collaboration request');
  });

  it('lists pending, received, and sent requests with default pagination', async () => {
    const userId = new Types.ObjectId().toString();
    const request = createRequestDoc();
    const { service, model } = createService();
    model.find.mockReturnValue(chain([request]));
    model.countDocuments.mockReturnValue(chain(1));

    await service.getMine(userId, {});
    expect(model.find).toHaveBeenLastCalledWith({
      targetUserId: new Types.ObjectId(userId),
      status: 'pending',
    });

    await service.listSent(userId, {});
    expect(model.find).toHaveBeenLastCalledWith({ requesterId: new Types.ObjectId(userId) });
  });

  it('cancels a pending request and remains successful if notification delivery fails', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const request = createRequestDoc({
      postId: null,
      requesterId: new Types.ObjectId(requesterId),
      targetUserId: new Types.ObjectId(targetUserId),
    });
    const { service, model, notificationsService } = createService();

    await expect(service.cancel(requesterId, 'invalid')).rejects.toThrow(
      'Invalid collaboration request id',
    );
    model.findById.mockReturnValueOnce(chain(null));
    await expect(service.cancel(requesterId, request.id)).rejects.toThrow(
      'Collaboration request not found',
    );

    model.findById.mockReturnValue(chain(request));
    model.findOne.mockReturnValue(chain({ ...request, status: 'cancelled' }));
    notificationsService.create.mockRejectedValue('push offline');

    await expect(service.cancel(requesterId, request.id)).resolves.toMatchObject({
      cancelled: true,
      request: expect.objectContaining({ status: 'cancelled' }),
    });
    expect(request.status).toBe('cancelled');
    expect(request.save).toHaveBeenCalled();
    expect(notificationsService.create).toHaveBeenCalledWith(
      expect.objectContaining({
        type: 'collaboration_request_cancelled',
        resourceType: 'user',
        referenceId: requesterId,
      }),
    );
  });

  it('rejects a pending request and protects status transitions', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const request = createRequestDoc({
      requesterId: new Types.ObjectId(requesterId),
      targetUserId: new Types.ObjectId(targetUserId),
    });
    const { service, model, notificationsService } = createService();

    await expect(service.reject(targetUserId, 'invalid')).rejects.toThrow(
      'Invalid collaboration request id',
    );
    model.findById.mockReturnValueOnce(chain(null));
    await expect(service.reject(targetUserId, request.id)).rejects.toThrow(
      'Collaboration request not found',
    );

    model.findById.mockReturnValueOnce(
      chain(createRequestDoc({ targetUserId: new Types.ObjectId(), requesterId: request.requesterId })),
    );
    await expect(service.reject(targetUserId, request.id)).rejects.toThrow(
      'Only the target user can update this collaboration request',
    );

    model.findById.mockReturnValueOnce(chain({ ...request, status: 'approved' }));
    await expect(service.reject(targetUserId, request.id)).rejects.toThrow(
      'Only pending collaboration requests can be updated',
    );

    model.findById.mockReturnValue(chain(request));
    model.findOne.mockReturnValue(chain({ ...request, status: 'rejected' }));
    await expect(service.reject(targetUserId, request.id)).resolves.toMatchObject({
      rejected: true,
      request: expect.objectContaining({ status: 'rejected' }),
    });
    expect(notificationsService.create).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'collaboration_request_rejected' }),
    );
  });

  it('stores a validated audio attachment and updates an empty duplicate request safely', async () => {
    const requesterId = new Types.ObjectId().toString();
    const targetUserId = new Types.ObjectId().toString();
    const existing = createRequestDoc({
      postId: null,
      requesterId: new Types.ObjectId(requesterId),
      targetUserId: new Types.ObjectId(targetUserId),
    });
    const { service, model, usersRepository, blocksRepository, storageService } = createService();
    usersRepository.findById.mockResolvedValue({ isDisabled: false });
    blocksRepository.findAnyBetween.mockResolvedValue(null);
    model.findOne
      .mockReturnValueOnce(chain(null))
      .mockReturnValueOnce(chain({ ...existing, attachmentUrl: '/audio.mp3' }));
    model.findOneAndUpdate.mockReturnValue(chain(existing));

    await service.createGeneral(
      requesterId,
      targetUserId,
      { message: ' audio ' },
      {
        buffer: Buffer.from('audio'),
        originalname: 'sample.mp3',
        mimetype: 'audio/mpeg',
        size: 5,
      },
    );
    expect(storageService.saveFile).toHaveBeenCalledWith(
      expect.objectContaining({
        folderSegments: ['collaboration', 'audio'],
        extension: '.mp3',
        fileNamePrefix: 'collaboration',
      }),
    );

    const duplicate = createService();
    duplicate.usersRepository.findById.mockResolvedValue({ isDisabled: false });
    duplicate.blocksRepository.findAnyBetween.mockResolvedValue(null);
    duplicate.model.findOne
      .mockReturnValueOnce(chain(existing))
      .mockReturnValueOnce(chain(existing));
    duplicate.model.findById.mockReturnValue(chain(existing));
    await duplicate.service.createGeneral(requesterId, targetUserId, {});
    expect(duplicate.model.findById).toHaveBeenCalledWith(existing.id);
    expect(duplicate.model.findByIdAndUpdate).not.toHaveBeenCalled();
  });

  it('rejects invisible request details and invalid audio data', async () => {
    const userId = new Types.ObjectId().toString();
    const requestId = new Types.ObjectId().toString();
    const { service, model } = createService();

    await expect(service.getById(userId, 'invalid')).rejects.toThrow(
      'Invalid collaboration request id',
    );
    model.findOne.mockReturnValue(chain(null));
    await expect(service.getById(userId, requestId)).rejects.toThrow(
      'Collaboration request not found',
    );

    await expect(
      (service as any).saveCollaborationAttachment({
        buffer: Buffer.alloc(0),
        originalname: 'empty.mp3',
        mimetype: 'audio/mpeg',
        size: 0,
      }),
    ).rejects.toThrow('Invalid audio attachment');
  });
});
