/**
 * @copyright NHCarrigan
 * @license Naomi's Public License
 * @author Naomi Carrigan
 */
import {
  ReportStatus as PrismaReportStatus,
  ReportReason as PrismaReportReason,
} from "@prisma/client";
import type {
  CreateCommentReportDto,
  CommentReportWithDetails,
  ReportStatus,
  UpdateCommentReportDto,
} from "@library/shared-types";
import { ReportReason } from "@library/shared-types";
import { prisma } from "../lib/prisma.js";

export class CommentReportService {
  private prisma = prisma;

  /**
   * Convert Prisma ReportReason to shared-types ReportReason
   */
  private toPrismaReportReason(reason: ReportReason): PrismaReportReason {
    return reason as unknown as PrismaReportReason;
  }

  /**
   * Convert Prisma ReportStatus to shared-types ReportStatus
   */
  private toPrismaReportStatus(status: ReportStatus): PrismaReportStatus {
    return status as unknown as PrismaReportStatus;
  }

  /**
   * Convert Prisma enum back to shared-types enum
   */
  private fromPrismaReportReason(reason: PrismaReportReason): ReportReason {
    return reason as unknown as ReportReason;
  }

  /**
   * Convert Prisma enum back to shared-types enum
   */
  private fromPrismaReportStatus(status: PrismaReportStatus): ReportStatus {
    return status as unknown as ReportStatus;
  }

  /**
   * Create a new comment report.
   *
   * @param reporterId - The ID of the user making the report
   * @param createDto - The report details
   * @returns The created report
   * @throws Error if user already has a pending report for this comment
   */
  async createReport(
    reporterId: string,
    createDto: CreateCommentReportDto,
  ): Promise<CommentReportWithDetails> {
    // Check if user already has a pending report for this comment
    const existingReport = await this.prisma.commentReport.findFirst({
      where: {
        reporterId,
        reportedCommentId: createDto.reportedCommentId,
        status: PrismaReportStatus.PENDING,
      },
    });

    if (existingReport) {
      throw new Error(
        "You already have a pending report for this comment. Please wait for it to be reviewed.",
      );
    }

    // Check if user has reached the limit of pending reports (5 max)
    const pendingReportsCount = await this.prisma.commentReport.count({
      where: {
        reporterId,
        status: PrismaReportStatus.PENDING,
      },
    });

    if (pendingReportsCount >= 5) {
      throw new Error(
        "You have reached the maximum number of pending reports (5). Please wait for your existing reports to be reviewed.",
      );
    }

    const report = await this.prisma.commentReport.create({
      data: {
        reporterId,
        reportedCommentId: createDto.reportedCommentId,
        reason: this.toPrismaReportReason(createDto.reason),
        details: createDto.details,
      },
      include: {
        reportedComment: {
          include: {
            user: {
              select: {
                id: true,
                username: true,
                displayName: true,
                avatar: true,
              },
            },
          },
        },
        reporter: {
          select: {
            id: true,
            username: true,
            displayName: true,
            avatar: true,
          },
        },
      },
    });

    return {
      id: report.id,
      reportedCommentId: report.reportedCommentId,
      reporterId: report.reporterId,
      reason: this.fromPrismaReportReason(report.reason),
      details: report.details,
      status: this.fromPrismaReportStatus(report.status),
      reviewedBy: report.reviewedBy ?? undefined,
      reviewNotes: report.reviewNotes ?? undefined,
      createdAt: report.createdAt,
      updatedAt: report.updatedAt,
      reportedComment: {
        id: report.reportedComment.id,
        content: report.reportedComment.content,
        rawContent: report.reportedComment.rawContent ?? undefined,
        userId: report.reportedComment.userId,
        user: report.reportedComment.user,
      },
      reporter: report.reporter,
    };
  }

  /**
   * Get all comment reports (admin only). Optionally filter by status.
   *
   * @param status - Optional status filter
   * @returns All reports matching the filter
   */
  async getAllReports(
    status?: ReportStatus,
  ): Promise<CommentReportWithDetails[]> {
    const reports = await this.prisma.commentReport.findMany({
      where: status ? { status: this.toPrismaReportStatus(status) } : undefined,
      include: {
        reportedComment: {
          include: {
            user: {
              select: {
                id: true,
                username: true,
                displayName: true,
                avatar: true,
              },
            },
          },
        },
        reporter: {
          select: {
            id: true,
            username: true,
            displayName: true,
            avatar: true,
          },
        },
        reviewer: {
          select: {
            id: true,
            username: true,
            displayName: true,
          },
        },
      },
      orderBy: {
        createdAt: "desc",
      },
    });

    return reports.map((report) => ({
      id: report.id,
      reportedCommentId: report.reportedCommentId,
      reporterId: report.reporterId,
      reason: this.fromPrismaReportReason(report.reason),
      details: report.details,
      status: this.fromPrismaReportStatus(report.status),
      reviewedBy: report.reviewedBy ?? undefined,
      reviewNotes: report.reviewNotes ?? undefined,
      createdAt: report.createdAt,
      updatedAt: report.updatedAt,
      reportedComment: {
        id: report.reportedComment.id,
        content: report.reportedComment.content,
        rawContent: report.reportedComment.rawContent ?? undefined,
        userId: report.reportedComment.userId,
        user: report.reportedComment.user,
      },
      reporter: report.reporter,
      reviewer: report.reviewer ?? undefined,
    }));
  }

  /**
   * Get a single comment report by ID (admin only).
   *
   * @param id - The report ID
   * @returns The report or null
   */
  async getReportById(id: string): Promise<CommentReportWithDetails | null> {
    const report = await this.prisma.commentReport.findUnique({
      where: { id },
      include: {
        reportedComment: {
          include: {
            user: {
              select: {
                id: true,
                username: true,
                displayName: true,
                avatar: true,
              },
            },
          },
        },
        reporter: {
          select: {
            id: true,
            username: true,
            displayName: true,
            avatar: true,
          },
        },
        reviewer: {
          select: {
            id: true,
            username: true,
            displayName: true,
          },
        },
      },
    });

    if (!report) {
      return null;
    }

    return {
      id: report.id,
      reportedCommentId: report.reportedCommentId,
      reporterId: report.reporterId,
      reason: this.fromPrismaReportReason(report.reason),
      details: report.details,
      status: this.fromPrismaReportStatus(report.status),
      reviewedBy: report.reviewedBy ?? undefined,
      reviewNotes: report.reviewNotes ?? undefined,
      createdAt: report.createdAt,
      updatedAt: report.updatedAt,
      reportedComment: {
        id: report.reportedComment.id,
        content: report.reportedComment.content,
        rawContent: report.reportedComment.rawContent ?? undefined,
        userId: report.reportedComment.userId,
        user: report.reportedComment.user,
      },
      reporter: report.reporter,
      reviewer: report.reviewer ?? undefined,
    };
  }

  /**
   * Update a comment report's status and review notes (admin only).
   *
   * @param id - The report ID
   * @param reviewerId - The ID of the admin reviewing the report
   * @param updateDto - The update details
   * @returns The updated report
   */
  async updateReport(
    id: string,
    reviewerId: string,
    updateDto: UpdateCommentReportDto,
  ): Promise<CommentReportWithDetails> {
    const report = await this.prisma.commentReport.update({
      where: { id },
      data: {
        status: this.toPrismaReportStatus(updateDto.status),
        reviewNotes: updateDto.reviewNotes,
        reviewedBy: reviewerId,
      },
      include: {
        reportedComment: {
          include: {
            user: {
              select: {
                id: true,
                username: true,
                displayName: true,
                avatar: true,
              },
            },
          },
        },
        reporter: {
          select: {
            id: true,
            username: true,
            displayName: true,
            avatar: true,
          },
        },
        reviewer: {
          select: {
            id: true,
            username: true,
            displayName: true,
          },
        },
      },
    });

    return {
      id: report.id,
      reportedCommentId: report.reportedCommentId,
      reporterId: report.reporterId,
      reason: this.fromPrismaReportReason(report.reason),
      details: report.details,
      status: this.fromPrismaReportStatus(report.status),
      reviewedBy: report.reviewedBy ?? undefined,
      reviewNotes: report.reviewNotes ?? undefined,
      createdAt: report.createdAt,
      updatedAt: report.updatedAt,
      reportedComment: {
        id: report.reportedComment.id,
        content: report.reportedComment.content,
        rawContent: report.reportedComment.rawContent ?? undefined,
        userId: report.reportedComment.userId,
        user: report.reportedComment.user,
      },
      reporter: report.reporter,
      reviewer: report.reviewer ?? undefined,
    };
  }

  /**
   * Check if a comment has any pending reports.
   *
   * @param commentId - The comment ID
   * @returns True if the comment has pending reports
   */
  async hasPendingReports(commentId: string): Promise<boolean> {
    const count = await this.prisma.commentReport.count({
      where: {
        reportedCommentId: commentId,
        status: PrismaReportStatus.PENDING,
      },
    });

    return count > 0;
  }
}
