import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection, Types } from 'mongoose';
import { ModerationStatus } from '../../common/enums/moderation-status.enum';
import { buildPaginatedResponse } from '../../common/utils/pagination.util';
import { resolveMongoSortDirection } from '../../common/utils/sort.util';
import { BlocksService } from '../blocks/blocks.service';
import { CreateReportDto } from './dto/create-report.dto';
import { ReportQueryDto } from './dto/report-query.dto';
import { UpdateReportStatusDto } from './dto/update-report-status.dto';
import { ReportsRepository } from './reports.repository';

@Injectable()
export class ReportsService {
  constructor(
    @InjectConnection() private readonly connection: Connection,
    private readonly reportsRepository: ReportsRepository,
    private readonly blocksService: BlocksService,
  ) {}

  async create(reporterId: string, dto: CreateReportDto) {
    await this.assertTargetExists(dto.targetType, dto.targetId);
    const report = await this.reportsRepository.create({
      reporterId,
      targetType: dto.targetType,
      targetId: dto.targetId,
      reason: dto.reason,
      details: dto.details?.trim() ?? '',
    });

    const automaticModeration = await this.applyAutomaticModeration(dto.targetType, dto.targetId, dto.reason);
    const safetyActions = await this.applyReporterSafetyActions(reporterId, dto);
    return { reported: true, item: report, automaticModeration, safetyActions };
  }

  async listMine(reporterId: string, query: ReportQueryDto) {
    return this.list({ ...this.buildFilter(query), reporterId: new Types.ObjectId(reporterId) }, query);
  }

  async listForSuperAdmin(query: ReportQueryDto) {
    return this.list(this.buildFilter(query), query);
  }

  async updateStatus(superAdminIdentifier: string, reportId: string, dto: UpdateReportStatusDto) {
    if (!Types.ObjectId.isValid(reportId)) {
      throw new NotFoundException('Report not found');
    }

    const updated = await this.reportsRepository.updateStatus(
      reportId,
      dto.status,
      dto.resolutionNote?.trim() ?? '',
      superAdminIdentifier,
    );
    if (!updated) {
      throw new NotFoundException('Report not found');
    }
    return updated;
  }

  private async list(filter: Record<string, unknown>, query: ReportQueryDto) {
    const page = query.page ?? 1;
    const limit = query.limit ?? 20;
    const skip = (page - 1) * limit;
    const sort = { createdAt: resolveMongoSortDirection(query.sortOrder) } as Record<string, 1 | -1>;
    const [items, total] = await Promise.all([
      this.reportsRepository.findMany(filter, skip, limit, sort),
      this.reportsRepository.count(filter),
    ]);

    return buildPaginatedResponse(items, { page, limit, total, offset: skip });
  }

  private buildFilter(query: ReportQueryDto): Record<string, unknown> {
    const filter: Record<string, unknown> = {};
    if (query.targetType) {
      filter.targetType = query.targetType;
    }
    if (query.status) {
      filter.status = query.status;
    }
    return filter;
  }

  private async assertTargetExists(targetType: string, targetId: string): Promise<void> {
    if (!Types.ObjectId.isValid(targetId)) {
      throw new BadRequestException('Invalid target id');
    }
    const collectionNameByType: Record<string, string> = {
      user: 'users',
      post: 'posts',
      comment: 'comments',
      listing: 'instruments',
      repair_shop: 'repairshops',
    };
    const collectionName = collectionNameByType[targetType];
    const target = collectionName
      ? await this.connection.collection(collectionName).findOne({ _id: new Types.ObjectId(targetId) })
      : null;
    if (!target) {
      throw new NotFoundException('Report target not found');
    }
  }

  private async applyAutomaticModeration(targetType: string, targetId: string, reason: string) {
    if (!['post', 'comment'].includes(targetType)) {
      return { status: 'not_applicable' };
    }

    const openReportsCount = await this.connection.collection('reports').countDocuments({
      targetType,
      targetId: new Types.ObjectId(targetId),
      status: { $in: ['open', 'in_review'] },
    });

    if (openReportsCount < 3) {
      return { status: 'watching', openReportsCount };
    }

    const collection = targetType === 'post' ? 'posts' : 'comments';
    await this.connection.collection(collection).updateOne(
      { _id: new Types.ObjectId(targetId) },
      {
        $set: {
          moderationStatus: ModerationStatus.FLAGGED,
          moderationReason: `Automatically flagged after ${openReportsCount} reports. Latest reason: ${reason}`,
        },
      },
    );

    return { status: ModerationStatus.FLAGGED, openReportsCount };
  }

  private async applyReporterSafetyActions(reporterId: string, dto: CreateReportDto) {
    if (dto.targetType !== 'user') {
      return { blockAvailable: false, blocked: false };
    }

    if (!dto.blockTarget) {
      return { blockAvailable: true, blocked: false };
    }

    await this.blocksService.block(reporterId, dto.targetId);
    return { blockAvailable: true, blocked: true };
  }
}
