import { Logger } from "@nestjs/common";
import { marked } from "marked";
import { gfmHeadingId } from "marked-gfm-heading-id";
import { markedHighlight } from "marked-highlight";
import hljs from "highlight.js";
import sanitizeHtml from "sanitize-html";

const logger = new Logger("MarkdownRenderer");

/**
 * Configure marked with GFM, syntax highlighting, and security features
 */
function configureMarked(): void {
  // Add GFM heading ID extension
  marked.use(gfmHeadingId());

  // Add syntax highlighting extension
  marked.use(
    markedHighlight({
      langPrefix: "hljs language-",
      highlight(code: string, lang: string): string {
        const language = hljs.getLanguage(lang) ? lang : "plaintext";
        return hljs.highlight(code, { language }).value;
      },
    })
  );

  // Configure marked options with GFM extensions
  marked.use({
    gfm: true, // GitHub Flavored Markdown
    breaks: false, // Don't convert \n to <br>
    pedantic: false,
  });
}

// Initialize configuration
configureMarked();

/**
 * Sanitization options for HTML output
 * Allows safe HTML tags while preventing XSS attacks
 */
const SANITIZE_OPTIONS: sanitizeHtml.IOptions = {
  allowedTags: [
    // Text formatting
    "p",
    "br",
    "strong",
    "em",
    "u",
    "s",
    "del",
    "mark",
    "small",
    "sub",
    "sup",
    // Headers
    "h1",
    "h2",
    "h3",
    "h4",
    "h5",
    "h6",
    // Lists
    "ul",
    "ol",
    "li",
    // Links and media
    "a",
    "img",
    // Code
    "code",
    "pre",
    "kbd",
    "samp",
    "var",
    // Tables
    "table",
    "thead",
    "tbody",
    "tfoot",
    "tr",
    "th",
    "td",
    // Quotes and citations
    "blockquote",
    "cite",
    "q",
    // Structure
    "div",
    "span",
    "hr",
    // Task lists (input checkboxes)
    "input",
  ],
  allowedAttributes: {
    a: ["href", "target", "rel"], // Removed title to prevent event handler injection
    img: ["src", "alt", "title", "width", "height"],
    code: ["class"], // For syntax highlighting
    pre: ["class"],
    h1: ["id"],
    h2: ["id"],
    h3: ["id"],
    h4: ["id"],
    h5: ["id"],
    h6: ["id"],
    td: ["align"],
    th: ["align"],
    input: ["type", "checked", "disabled"], // For task lists
  },
  allowedSchemes: ["http", "https", "mailto"],
  allowedSchemesByTag: {
    img: ["http", "https"],
  },
  allowedClasses: {
    code: ["hljs", "language-*"],
    pre: ["hljs"],
  },
  allowedIframeHostnames: [], // No iframes allowed
  // Enforce target="_blank" and rel="noopener noreferrer" for external links
  // Block data: URIs in links and images to prevent XSS/CSRF attacks
  transformTags: {
    a: (tagName: string, attribs: sanitizeHtml.Attributes) => {
      const href = attribs.href;
      // Strip data: URI scheme from links
      if (href?.trim().toLowerCase().startsWith("data:")) {
        logger.warn(`Blocked data: URI in link href`);
        const { href: _removed, ...safeAttribs } = attribs;
        return {
          tagName,
          attribs: safeAttribs,
        };
      }
      if (href && (href.startsWith("http://") || href.startsWith("https://"))) {
        return {
          tagName,
          attribs: {
            ...attribs,
            target: "_blank",
            rel: "noopener noreferrer",
          },
        };
      }
      return {
        tagName,
        attribs,
      };
    },
    // Strip data: URI scheme from images to prevent XSS/CSRF
    img: (tagName: string, attribs: sanitizeHtml.Attributes) => {
      const src = attribs.src;
      if (src?.trim().toLowerCase().startsWith("data:")) {
        logger.warn(`Blocked data: URI in image src`);
        const { src: _removed, ...safeAttribs } = attribs;
        return {
          tagName,
          attribs: safeAttribs,
        };
      }
      return {
        tagName,
        attribs,
      };
    },
    // Disable task list checkboxes (make them read-only)
    input: (tagName: string, attribs: sanitizeHtml.Attributes) => {
      if (attribs.type === "checkbox") {
        return {
          tagName,
          attribs: {
            ...attribs,
            disabled: "disabled",
          },
        };
      }
      return {
        tagName,
        attribs,
      };
    },
  },
};

/**
 * Render markdown content to sanitized HTML
 * Supports GFM (tables, task lists, strikethrough) and syntax highlighting
 *
 * @param markdown - Raw markdown content
 * @returns Sanitized HTML string
 * @throws Error if rendering fails
 */
export async function renderMarkdown(markdown: string): Promise<string> {
  if (!markdown || typeof markdown !== "string") {
    return "";
  }

  try {
    // Parse markdown to HTML
    const rawHtml = await marked.parse(markdown);

    // Sanitize HTML to prevent XSS
    const safeHtml = sanitizeHtml(rawHtml, SANITIZE_OPTIONS);

    return safeHtml;
  } catch (error) {
    // Log error but don't expose internal details
    logger.error("Markdown rendering error:", error);
    throw new Error("Failed to render markdown content", { cause: error });
  }
}

/**
 * Render markdown synchronously (for simple use cases)
 * Note: Use async version when possible
 *
 * @param markdown - Raw markdown content
 * @returns Sanitized HTML string
 */
export function renderMarkdownSync(markdown: string): string {
  if (!markdown || typeof markdown !== "string") {
    return "";
  }

  try {
    // Parse markdown to HTML (sync version)
    const rawHtml = marked.parse(markdown) as string;

    // Sanitize HTML to prevent XSS
    const safeHtml = sanitizeHtml(rawHtml, SANITIZE_OPTIONS);

    return safeHtml;
  } catch (error) {
    logger.error("Markdown rendering error:", error);
    throw new Error("Failed to render markdown content", { cause: error });
  }
}

/**
 * Strip HTML tags from rendered markdown (extract plain text)
 * Useful for generating summaries or search indexes
 *
 * @param markdown - Raw markdown content
 * @returns Plain text without HTML tags
 */
export async function markdownToPlainText(markdown: string): Promise<string> {
  const html = await renderMarkdown(markdown);
  return sanitizeHtml(html, {
    allowedTags: [],
    allowedAttributes: {},
  });
}
