import { BadRequestException, NotFoundException } from '@nestjs/common';
import { Types } from 'mongoose';
import { ModerationStatus } from '../../common/enums/moderation-status.enum';
import { ReportsService } from './reports.service';

describe('ReportsService', () => {
  const reporterId = new Types.ObjectId().toHexString();
  const targetId = new Types.ObjectId().toHexString();
  const reportId = new Types.ObjectId().toHexString();

  const setup = () => {
    const collections = {
      users: { findOne: jest.fn().mockResolvedValue({ _id: targetId }) },
      posts: {
        findOne: jest.fn().mockResolvedValue({ _id: targetId }),
        updateOne: jest.fn().mockResolvedValue({ acknowledged: true }),
      },
      comments: {
        findOne: jest.fn().mockResolvedValue({ _id: targetId }),
        updateOne: jest.fn().mockResolvedValue({ acknowledged: true }),
      },
      instruments: { findOne: jest.fn().mockResolvedValue({ _id: targetId }) },
      repairshops: { findOne: jest.fn().mockResolvedValue({ _id: targetId }) },
      reports: { countDocuments: jest.fn().mockResolvedValue(1) },
    };
    const connection = {
      collection: jest.fn((name: keyof typeof collections) => collections[name]),
    };
    const repository = {
      create: jest.fn().mockResolvedValue({ id: reportId }),
      findMany: jest.fn().mockResolvedValue([{ id: reportId }]),
      count: jest.fn().mockResolvedValue(1),
      updateStatus: jest.fn().mockResolvedValue({ id: reportId, status: 'resolved' }),
    };
    const blocksService = { block: jest.fn().mockResolvedValue({ blocked: true }) };
    const service = new ReportsService(connection as any, repository as any, blocksService as any);
    return { service, connection, repository, blocksService, collections };
  };

  it('creates a report, trims details, and keeps low-volume targets under observation', async () => {
    const { service, repository, collections } = setup();

    await expect(
      service.create(reporterId, {
        targetType: 'post',
        targetId,
        reason: 'spam' as any,
        details: '  repeated links  ',
      }),
    ).resolves.toEqual({
      reported: true,
      item: { id: reportId },
      automaticModeration: { status: 'watching', openReportsCount: 1 },
      safetyActions: { blockAvailable: false, blocked: false },
    });
    expect(repository.create).toHaveBeenCalledWith(
      expect.objectContaining({ details: 'repeated links', reporterId, targetId }),
    );
    expect(collections.posts.updateOne).not.toHaveBeenCalled();
  });

  it.each(['post', 'comment'] as const)('automatically flags a %s after three open reports', async (targetType) => {
    const { service, collections } = setup();
    collections.reports.countDocuments.mockResolvedValue(3);

    const result = await service.create(reporterId, {
      targetType,
      targetId,
      reason: 'harassment' as any,
    });

    expect(result.automaticModeration).toEqual({ status: ModerationStatus.FLAGGED, openReportsCount: 3 });
    expect(collections[targetType === 'post' ? 'posts' : 'comments'].updateOne).toHaveBeenCalledWith(
      { _id: expect.any(Types.ObjectId) },
      { $set: expect.objectContaining({ moderationStatus: ModerationStatus.FLAGGED }) },
    );
  });

  it('can block a reported user as an immediate safety action', async () => {
    const { service, blocksService } = setup();

    const result = await service.create(reporterId, {
      targetType: 'user',
      targetId,
      reason: 'harassment' as any,
      blockTarget: true,
    });

    expect(result.automaticModeration).toEqual({ status: 'not_applicable' });
    expect(result.safetyActions).toEqual({ blockAvailable: true, blocked: true });
    expect(blocksService.block).toHaveBeenCalledWith(reporterId, targetId);
  });

  it('advertises blocking without doing it when a user report does not request it', async () => {
    const { service, blocksService } = setup();

    const result = await service.create(reporterId, {
      targetType: 'user',
      targetId,
      reason: 'other' as any,
    });

    expect(result.safetyActions).toEqual({ blockAvailable: true, blocked: false });
    expect(blocksService.block).not.toHaveBeenCalled();
  });

  it('rejects malformed and missing report targets', async () => {
    const { service, collections } = setup();
    await expect(
      service.create(reporterId, { targetType: 'post', targetId: 'bad', reason: 'spam' as any }),
    ).rejects.toBeInstanceOf(BadRequestException);

    collections.posts.findOne.mockResolvedValueOnce(null);
    await expect(
      service.create(reporterId, { targetType: 'post', targetId, reason: 'spam' as any }),
    ).rejects.toBeInstanceOf(NotFoundException);
  });

  it('paginates the current user reports with filters and descending defaults', async () => {
    const { service, repository } = setup();

    const result = await service.listMine(reporterId, {
      page: 2,
      limit: 5,
      targetType: 'post',
      status: 'open',
    } as any);

    expect(repository.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        reporterId: expect.any(Types.ObjectId),
        targetType: 'post',
        status: 'open',
      }),
      5,
      5,
      { createdAt: -1 },
    );
    expect(result).toEqual(expect.objectContaining({ items: [{ id: reportId }] }));
  });

  it('lists all reports with default pagination and an empty filter', async () => {
    const { service, repository } = setup();

    await service.listForSuperAdmin({} as any);

    expect(repository.findMany).toHaveBeenCalledWith({}, 0, 20, { createdAt: -1 });
    expect(repository.count).toHaveBeenCalledWith({});
  });

  it('updates status and trims the resolution note', async () => {
    const { service, repository } = setup();

    await expect(
      service.updateStatus('admin@example.com', reportId, {
        status: 'resolved',
        resolutionNote: '  actioned  ',
      } as any),
    ).resolves.toEqual({ id: reportId, status: 'resolved' });
    expect(repository.updateStatus).toHaveBeenCalledWith(reportId, 'resolved', 'actioned', 'admin@example.com');
  });

  it('returns not found for malformed or absent reports during an update', async () => {
    const { service, repository } = setup();
    await expect(service.updateStatus('admin', 'bad', { status: 'resolved' } as any)).rejects.toBeInstanceOf(
      NotFoundException,
    );

    repository.updateStatus.mockResolvedValueOnce(null);
    await expect(
      service.updateStatus('admin', reportId, { status: 'resolved' } as any),
    ).rejects.toBeInstanceOf(NotFoundException);
  });
});
