/**
 * @copyright 2026 NHCarrigan
 * @license Naomi's Public License
 * @author Naomi Carrigan
 */

import { Comment, CreateCommentDto, PrimaryBadge } from "@library/shared-types";
import { prisma } from "../lib/prisma";
import createDOMPurify from "dompurify";
import { JSDOM } from "jsdom";
import { marked } from "marked";
import { validateStringLength, MAX_LENGTHS } from "../utils/validation";

const window = new JSDOM("").window;
const DOMPurify = createDOMPurify(window);

// Add hook to sanitise links - prevent javascript: URLs and add security attributes
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
  if (node.tagName === "A") {
    const href = node.getAttribute("href") || "";
    // Block javascript:, data:, and vbscript: URLs
    if (/^(javascript|data|vbscript):/i.test(href)) {
      node.removeAttribute("href");
    } else {
      // Add security attributes to external links
      node.setAttribute("target", "_blank");
      node.setAttribute("rel", "noopener noreferrer nofollow");
    }
  }
});

export class CommentService {
  private prisma = prisma;

  constructor() {}

  private sanitizeMarkdown(content: string): string {
    // Validate content length before processing
    if (!validateStringLength(content, MAX_LENGTHS.COMMENT_CONTENT)) {
      throw new Error(`Comment must be ${MAX_LENGTHS.COMMENT_CONTENT} characters or less.`);
    }

    const html = marked.parse(content, { async: false }) as string;
    return DOMPurify.sanitize(html, {
      ALLOWED_TAGS: [
        "p", "br", "strong", "em", "b", "i", "u", "s", "strike",
        "h1", "h2", "h3", "h4", "h5", "h6",
        "ul", "ol", "li",
        "blockquote", "code", "pre",
        "a", "hr",
      ],
      ALLOWED_ATTR: ["href", "target", "rel"],
      ALLOW_DATA_ATTR: false,
      ADD_ATTR: ["target", "rel"],
      FORCE_BODY: true,
    });
  }

  private async mapComment(comment: any): Promise<Comment> {
    // Check if comment has pending reports
    const hasPendingReports = comment.reports
      ? comment.reports.some((report: any) => report.status === "PENDING")
      : false;

    return {
      id: comment.id,
      content: comment.content,
      rawContent: comment.rawContent || undefined,
      userId: comment.userId,
      user: {
        id: comment.user.id,
        username: comment.user.username,
        avatar: comment.user.avatar || undefined,
        primaryBadge: (comment.user.primaryBadge as PrimaryBadge) || undefined,
        inDiscord: comment.user.inDiscord,
        isVip: comment.user.isVip,
        isMod: comment.user.isMod,
        isStaff: comment.user.isStaff,
      },
      gameId: comment.gameId || undefined,
      bookId: comment.bookId || undefined,
      musicId: comment.musicId || undefined,
      artId: comment.artId || undefined,
      showId: comment.showId || undefined,
      mangaId: comment.mangaId || undefined,
      hasPendingReports,
      createdAt: comment.createdAt,
      updatedAt: comment.updatedAt,
    };
  }

  async getCommentsForGame(gameId: string): Promise<Comment[]> {
    const comments = await this.prisma.comment.findMany({
      where: { gameId },
      include: { user: true, reports: true },
      orderBy: { createdAt: "desc" },
    });
    return Promise.all(comments.map((c) => this.mapComment(c)));
  }

  async getCommentsForBook(bookId: string): Promise<Comment[]> {
    const comments = await this.prisma.comment.findMany({
      where: { bookId },
      include: { user: true, reports: true },
      orderBy: { createdAt: "desc" },
    });
    return Promise.all(comments.map((c) => this.mapComment(c)));
  }

  async getCommentsForMusic(musicId: string): Promise<Comment[]> {
    const comments = await this.prisma.comment.findMany({
      where: { musicId },
      include: { user: true, reports: true },
      orderBy: { createdAt: "desc" },
    });
    return Promise.all(comments.map((c) => this.mapComment(c)));
  }

  async createCommentForGame(
    gameId: string,
    userId: string,
    data: CreateCommentDto
  ): Promise<Comment> {
    const sanitizedContent = this.sanitizeMarkdown(data.content);
    const comment = await this.prisma.comment.create({
      data: {
        content: sanitizedContent,
        rawContent: data.content,
        userId,
        gameId,
      },
      include: { user: true, reports: true },
    });
    return this.mapComment(comment);
  }

  async createCommentForBook(
    bookId: string,
    userId: string,
    data: CreateCommentDto
  ): Promise<Comment> {
    const sanitizedContent = this.sanitizeMarkdown(data.content);
    const comment = await this.prisma.comment.create({
      data: {
        content: sanitizedContent,
        rawContent: data.content,
        userId,
        bookId,
      },
      include: { user: true, reports: true },
    });
    return this.mapComment(comment);
  }

  async createCommentForMusic(
    musicId: string,
    userId: string,
    data: CreateCommentDto
  ): Promise<Comment> {
    const sanitizedContent = this.sanitizeMarkdown(data.content);
    const comment = await this.prisma.comment.create({
      data: {
        content: sanitizedContent,
        rawContent: data.content,
        userId,
        musicId,
      },
      include: { user: true, reports: true },
    });
    return this.mapComment(comment);
  }

  async getCommentsForArt(artId: string): Promise<Comment[]> {
    const comments = await this.prisma.comment.findMany({
      where: { artId },
      include: { user: true, reports: true },
      orderBy: { createdAt: "desc" },
    });
    return Promise.all(comments.map((c) => this.mapComment(c)));
  }

  async createCommentForArt(
    artId: string,
    userId: string,
    data: CreateCommentDto
  ): Promise<Comment> {
    const sanitizedContent = this.sanitizeMarkdown(data.content);
    const comment = await this.prisma.comment.create({
      data: {
        content: sanitizedContent,
        rawContent: data.content,
        userId,
        artId,
      },
      include: { user: true, reports: true },
    });
    return this.mapComment(comment);
  }

  async getCommentsForShow(showId: string): Promise<Comment[]> {
    const comments = await this.prisma.comment.findMany({
      where: { showId },
      include: { user: true, reports: true },
      orderBy: { createdAt: "desc" },
    });
    return Promise.all(comments.map((c) => this.mapComment(c)));
  }

  async createCommentForShow(
    showId: string,
    userId: string,
    data: CreateCommentDto
  ): Promise<Comment> {
    const sanitizedContent = this.sanitizeMarkdown(data.content);
    const comment = await this.prisma.comment.create({
      data: {
        content: sanitizedContent,
        rawContent: data.content,
        userId,
        showId,
      },
      include: { user: true, reports: true },
    });
    return this.mapComment(comment);
  }

  async getCommentsForManga(mangaId: string): Promise<Comment[]> {
    const comments = await this.prisma.comment.findMany({
      where: { mangaId },
      include: { user: true, reports: true },
      orderBy: { createdAt: "desc" },
    });
    return Promise.all(comments.map((c) => this.mapComment(c)));
  }

  async createCommentForManga(
    mangaId: string,
    userId: string,
    data: CreateCommentDto
  ): Promise<Comment> {
    const sanitizedContent = this.sanitizeMarkdown(data.content);
    const comment = await this.prisma.comment.create({
      data: {
        content: sanitizedContent,
        rawContent: data.content,
        userId,
        mangaId,
      },
      include: { user: true, reports: true },
    });
    return this.mapComment(comment);
  }

  async getCommentById(commentId: string) {
    return this.prisma.comment.findUnique({
      where: { id: commentId },
      include: { user: true },
    });
  }

  async updateComment(
    commentId: string,
    content: string
  ): Promise<Comment> {
    const sanitizedContent = this.sanitizeMarkdown(content);
    const comment = await this.prisma.comment.update({
      where: { id: commentId },
      data: {
        content: sanitizedContent,
        rawContent: content,
      },
      include: { user: true, reports: true },
    });
    return this.mapComment(comment);
  }

  async deleteComment(commentId: string): Promise<void> {
    await this.prisma.comment.delete({
      where: { id: commentId },
    });
  }

  async verifyCommentOwnership(
    commentId: string,
    resourceType: "game" | "book" | "music" | "art" | "show" | "manga",
    resourceId: string
  ): Promise<{ exists: boolean; comment?: { userId: string } }> {
    const fieldMap = {
      game: "gameId",
      book: "bookId",
      music: "musicId",
      art: "artId",
      show: "showId",
      manga: "mangaId",
    };

    const comment = await this.prisma.comment.findFirst({
      where: {
        id: commentId,
        [fieldMap[resourceType]]: resourceId,
      },
      select: { userId: true },
    });

    return comment ? { exists: true, comment } : { exists: false };
  }
}
