import sharp from "sharp";
import satori from "satori";
import { readFileSync } from "fs";
import { join } from "path";
import React from "react";
import { DEFAULT_WATERMARK_PRESET } from "./watermark-preset";
import { getSavedWatermarkPreset } from "./watermark-config";
import { resolveWatermarkFont } from "./watermark-fonts";

interface TableData {
  headers: string[];
  rows: string[][];
}

interface RenderTableOptions {
  watermarkSubtitle?: string;
  watermarkDate?: string;
  watermarkOpacity?: number;
  watermarkOffsetX?: number;
  watermarkOffsetY?: number;
  watermarkStampGapX?: number;
  watermarkStampGapY?: number;
  watermarkFontFamily?: string;
}

const FONT_SIZE = 14;
const LINE_HEIGHT = 20;
const OUTER_PADDING = 20;
const CELL_HORIZONTAL_PADDING = 16;
const CELL_VERTICAL_PADDING = 10;
const MIN_TABLE_WIDTH = 600;
const COMPACT_TABLE_WIDTH = 400;
const WATERMARK_TEXT = "MYRISTICA";

// Load Geist font once (bundled with Next.js)
let fontData: Buffer | null = null;
function getFont(): Buffer {
  if (!fontData) {
    const fontPath = join(process.cwd(), "node_modules/next/dist/compiled/@vercel/og/Geist-Regular.ttf");
    fontData = readFileSync(fontPath);
  }
  return fontData;
}

/**
 * Parse a Markdown table from text content.
 */
export function parseMarkdownTable(text: string): { table: TableData | null; before: string; after: string; rawTable: string } {
  const lines = text.split("\n");
  for (let i = 0; i < lines.length - 1; i++) {
    const headers = parseTableRow(lines[i]);
    if (headers.length < 2 || !isSeparatorRow(lines[i + 1], headers.length)) continue;

    let tableEnd = i + 1;
    for (let j = i + 2; j < lines.length; j++) {
      if (!isTableRow(lines[j])) break;
      tableEnd = j;
    }

    const tableLines = lines.slice(i, tableEnd + 1);
    const rows = tableLines
      .slice(2)
      .map((line) => normalizeRow(parseTableRow(line), headers.length))
      .filter((row) => row.some((cell) => cell.length > 0));

    if (!rows.length) continue;

    const before = lines.slice(0, i).join("\n").trim();
    const after = lines.slice(tableEnd + 1).join("\n").trim();
    const enhanced = appendTotalRowIfNeeded({ headers, rows }, after);

    return {
      table: enhanced.table,
      before,
      after: enhanced.after,
      rawTable: toMarkdownTable(enhanced.table),
    };
  }

  return { table: null, before: text, after: "", rawTable: "" };
}

function parseTableRow(line: string): string[] {
  const trimmed = line.trim();
  if (!trimmed.includes("|")) return [];

  const withoutLeadingPipe = trimmed.startsWith("|") ? trimmed.slice(1) : trimmed;
  const withoutOuterPipes = withoutLeadingPipe.endsWith("|")
    ? withoutLeadingPipe.slice(0, -1)
    : withoutLeadingPipe;

  return withoutOuterPipes.split("|").map((cell) => stripMarkdownFormatting(cell.trim()));
}

function stripMarkdownFormatting(text: string): string {
  return text
    .replace(/\*\*(.+?)\*\*/g, "$1")
    .replace(/__(.+?)__/g, "$1")
    .replace(/^\*([^*]+)\*$/g, "$1")
    .replace(/^_([^_]+)_$/g, "$1")
    .replace(/`([^`]+)`/g, "$1")
    .trim();
}

function isTableRow(line: string): boolean {
  return parseTableRow(line).length > 1;
}

function isSeparatorRow(line: string, expectedColumns: number): boolean {
  const cells = parseTableRow(line);
  return (
    cells.length === expectedColumns &&
    cells.every((cell) => /^:?-{3,}:?$/.test(cell.replace(/\s+/g, "")))
  );
}

function normalizeRow(cells: string[], width: number): string[] {
  return Array.from({ length: width }, (_, index) => cells[index] ?? "");
}

function toMarkdownTable(table: TableData): string {
  const header = `| ${table.headers.join(" | ")} |`;
  const separator = `| ${table.headers.map(() => "---").join(" | ")} |`;
  const rows = table.rows.map((row) => `| ${normalizeRow(row, table.headers.length).join(" | ")} |`);
  return [header, separator, ...rows].join("\n");
}

function isCompactServiceDebtTable(table: TableData): boolean {
  if (table.headers.length !== 2) return false;

  const normalizedHeaders = table.headers.map((header) => stripMarkdownFormatting(header).toLowerCase());
  return normalizedHeaders[0] === "servicio" && normalizedHeaders[1] === "deuda";
}

function normalizeWatermarkLabel(text: string): string {
  return stripMarkdownFormatting(text)
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/\s+/g, " ")
    .trim()
    .toUpperCase();
}

function getWatermarkLayout(
  width: number,
  height: number,
  config?: {
    opacity?: number;
    offsetX?: number;
    offsetY?: number;
    stampGapX?: number;
    stampGapY?: number;
  }
){
  const overlayOpacity = config?.opacity ?? DEFAULT_WATERMARK_PRESET.opacity;
  const offsetX = config?.offsetX ?? 0;
  const offsetY = config?.offsetY ?? 0;
  const stampGapX = config?.stampGapX ?? DEFAULT_WATERMARK_PRESET.stampGapX;
  const stampGapY = config?.stampGapY ?? DEFAULT_WATERMARK_PRESET.stampGapY;
  const titleFontSize = 34;
  const subtitleFontSize = 14;
  const dateFontSize = 12;
  const lineGap = 4;
  const blockWidth = Math.max(260, Math.min(320, Math.floor(width * 0.5)));
  const blockHeight = titleFontSize + subtitleFontSize + dateFontSize + lineGap * 2 + 14;
  const slotWidth = blockWidth + stampGapX;
  const cols = Math.max(2, Math.ceil(width / (slotWidth * 2)) + 3);
  const rows = Math.max(5, Math.ceil(height / (blockHeight + stampGapY)) + 3);

  const stamps: Array<{ x: number; y: number }> = [];
  for (let row = 0; row < rows; row++) {
    for (let col = 0; col < cols; col++) {
      const x = (col * 2 + (row % 2)) * slotWidth - 120 + offsetX;
      const y = row * (blockHeight + stampGapY) - 40 + offsetY;
      stamps.push({ x, y });
    }
  }

  return {
    overlayOpacity,
    titleFontSize,
    subtitleFontSize,
    dateFontSize,
    blockWidth,
    stamps,
  };
}

async function renderWatermarkOverlayPng(
  width: number,
  height: number,
  title: string,
  subtitle: string,
  date: string,
  config?: {
    opacity?: number;
    offsetX?: number;
    offsetY?: number;
    stampGapX?: number;
    stampGapY?: number;
    fontFamily?: string;
  }
): Promise<Buffer> {
  const layout = getWatermarkLayout(width, height, config);
  const watermarkTitle = normalizeWatermarkLabel(title);
  const watermarkSubtitle = normalizeWatermarkLabel(subtitle);
  const watermarkDate = date;

  const element = React.createElement(
    "div",
    {
      style: {
        display: "flex",
        position: "relative",
        width,
        height,
        backgroundColor: "rgba(255,255,255,0)",
      },
    },
    React.createElement(
      "div",
      {
        style: {
          display: "flex",
          position: "absolute",
          left: 0,
          top: 0,
          width,
          height,
          overflow: "hidden",
        },
      },
      React.createElement(
        "div",
        {
          style: {
            display: "flex",
            position: "absolute",
            left: 0,
            top: 0,
            width,
            height,
            opacity: layout.overlayOpacity,
            transform: "rotate(-22deg)",
            transformOrigin: "center",
          },
        },
        ...layout.stamps.map((stamp, index) =>
          React.createElement(
            "div",
            {
              key: `wm-${index}`,
              style: {
                display: "flex",
                position: "absolute",
                left: stamp.x,
                top: stamp.y,
                width: layout.blockWidth,
                flexDirection: "column",
                alignItems: "center",
                justifyContent: "center",
                color: "#0f172a",
                fontFamily: "WatermarkFont",
              },
            },
            React.createElement(
              "div",
              {
                style: {
                  display: "flex",
                  fontSize: layout.titleFontSize,
                  fontWeight: 800,
                  lineHeight: 1,
                  letterSpacing: 0,
                },
              },
              watermarkTitle
            ),
            React.createElement(
              "div",
              {
                style: {
                  display: "flex",
                  fontSize: layout.subtitleFontSize,
                  fontWeight: 700,
                  lineHeight: 1.2,
                  textAlign: "center",
                },
              },
              watermarkSubtitle
            ),
            React.createElement(
              "div",
              {
                style: {
                  display: "flex",
                  fontSize: layout.dateFontSize,
                  fontWeight: 700,
                  lineHeight: 1.1,
                },
              },
              watermarkDate
            )
          )
        )
      )
    )
  );

  const requestedFontFamily = config?.fontFamily?.trim() || DEFAULT_WATERMARK_PRESET.fontFamily;
  let watermarkFont;

  try {
    watermarkFont = await resolveWatermarkFont(requestedFontFamily);
  } catch (error) {
    console.warn(`[TABLE] Falling back to default watermark font after failing to load "${requestedFontFamily}":`, error);
    watermarkFont = await resolveWatermarkFont(DEFAULT_WATERMARK_PRESET.fontFamily);
  }

  if (watermarkFont.format === "woff2") {
    console.warn(`[TABLE] Watermark font "${requestedFontFamily}" resolved as woff2, which satori does not support here. Falling back to default font.`);
    watermarkFont = await resolveWatermarkFont(DEFAULT_WATERMARK_PRESET.fontFamily);
  }

  const svg = await satori(element, {
    width,
    height,
    fonts: [
      {
        name: "Geist",
        data: getFont(),
        weight: 400,
        style: "normal",
      },
      {
        name: "WatermarkFont",
        data: watermarkFont.dataUri
          ? Buffer.from(watermarkFont.dataUri.split(",", 2)[1] ?? "", "base64")
          : getFont(),
        weight: 400,
        style: "normal",
      },
    ],
  });

  return sharp(Buffer.from(svg)).png().toBuffer();
}

function inferWatermarkSubtitle(table: TableData): string {
  const headers = table.headers.map((header) => normalizeWatermarkLabel(header));

  if (isCompactServiceDebtTable(table)) return "DEUDA DE SERVICIOS";
  if (headers.includes("DEUDA RESTANTE")) return "DISTRIBUCION DE SERVICIOS";
  if (headers.includes("MODO") && headers.includes("SERVICIO OBJETIVO")) return "PAGOS DE SERVICIOS";
  if (headers.includes("FECHA PAGO") && headers.includes("REGISTRADO POR")) return "PAGOS DE ALQUILER";
  if (headers.includes("ACORDADO") && headers.includes("ESPERADO IPC")) return "ALQUILER MENSUAL";
  if (headers.includes("SALDO") && headers.includes("DIAS MORA")) return "CUOTAS IMPAGAS";
  if (headers.includes("CONCEPTO") && headers.includes("MONTO")) return "DEUDA DE ALQUILER";
  if (headers.includes("FACTURADO") && headers.includes("CORRESPONDE") && headers.includes("DEUDA")) return "DETALLE DE SERVICIO";

  return "REPORTE MYRISTICA";
}

function isTotalRow(row: string[]): boolean {
  return row.some((cell, index) => index === 0 && /^total\b/i.test(stripMarkdownFormatting(cell)));
}

function appendTotalRowIfNeeded(table: TableData, after: string): { table: TableData; after: string } {
  if (!isCompactServiceDebtTable(table)) {
    return { table, after };
  }

  if (table.rows.some((row) => isTotalRow(row))) {
    return { table, after };
  }

  const lines = after.split("\n").map((line) => line.trim()).filter(Boolean);
  const totalIndex = lines.findIndex((line) => /^total(?:\s+deuda\s+servicios)?\s*:/i.test(line));
  if (totalIndex === -1) {
    return { table, after };
  }

  const totalLine = lines[totalIndex];
  const amountMatch = totalLine.match(/\$\s*[\d.,]+/);
  if (!amountMatch) {
    return { table, after };
  }

  const nextRows = [...table.rows, ["Total", amountMatch[0].replace(/\s+/g, "")]];
  const remainingLines = lines.filter((_, index) => index !== totalIndex);

  return {
    table: { ...table, rows: nextRows },
    after: remainingLines.join("\n").trim(),
  };
}

function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

function isNumericColumn(header: string): boolean {
  return /Monto|Total|Facturado|Corresponde|Pagado|Deuda|Penalidad|D[ií]as|%|Dif/i.test(header);
}

function isDebtColumn(header: string): boolean {
  return /Deuda|Pendiente|Penalidad|Mora|Diferencia|Dif/i.test(header);
}

function isPaymentColumn(header: string): boolean {
  return /Pagado|Pago|Abonado|Cobrado/i.test(header);
}

function parseAmount(cell: string): number | null {
  const trimmed = cell.replace(/\s+/g, "").trim();
  if (!/^-?\$?[\d.]+(?:,\d+)?$/.test(trimmed)) return null;

  const normalized = trimmed
    .replace(/\$/g, "")
    .replace(/\s+/g, "")
    .replace(/\./g, "")
    .replace(",", ".");

  const value = Number(normalized);
  return Number.isFinite(value) ? value : null;
}

function estimateColumnWidth(header: string, cells: string[]): number {
  const numeric = isNumericColumn(header);
  const minWidth = numeric ? 120 : 140;
  const maxWidth = numeric ? 220 : 320;
  const charWidth = FONT_SIZE * 0.62;

  const longest = [header, ...cells].reduce((max, value) => {
    return Math.max(max, value.replace(/\s+/g, " ").trim().length);
  }, 0);

  return clamp(Math.ceil(longest * charWidth) + CELL_HORIZONTAL_PADDING * 2, minWidth, maxWidth);
}

function estimateLineCount(text: string, width: number): number {
  const normalized = text.replace(/\s+/g, " ").trim();
  if (!normalized) return 1;

  const usableWidth = Math.max(width - CELL_HORIZONTAL_PADDING * 2, FONT_SIZE);
  const charsPerLine = Math.max(1, Math.floor(usableWidth / (FONT_SIZE * 0.62)));

  return normalized.split("\n").reduce((total, line) => {
    const trimmed = line.trim();
    return total + Math.max(1, Math.ceil((trimmed.length || 1) / charsPerLine));
  }, 0);
}

function estimateRowHeight(cells: string[], widths: number[]): number {
  const maxLines = cells.reduce((max, cell, index) => {
    return Math.max(max, estimateLineCount(cell, widths[index] ?? widths[widths.length - 1]));
  }, 1);

  return Math.max(LINE_HEIGHT + CELL_VERTICAL_PADDING * 2, maxLines * LINE_HEIGHT + CELL_VERTICAL_PADDING * 2);
}

function distributeExtraWidth(widths: number[], minTotalWidth: number): number[] {
  const currentWidth = widths.reduce((sum, width) => sum + width, 0);
  const extraWidth = Math.max(0, minTotalWidth - currentWidth);
  if (extraWidth === 0) return widths;

  const bonusPerColumn = Math.floor(extraWidth / widths.length);
  const remainder = extraWidth % widths.length;

  return widths.map((width, index) => width + bonusPerColumn + (index < remainder ? 1 : 0));
}

/**
 * Render a table to PNG using satori (text→SVG paths) + sharp (SVG→PNG).
 */
export async function renderTableToImage(table: TableData, options?: RenderTableOptions): Promise<Buffer | null> {
  if (!table.headers.length || !table.rows.length) return null;
  const isCompactDebtSummary = isCompactServiceDebtTable(table);
  const watermarkSubtitle = normalizeWatermarkLabel(options?.watermarkSubtitle ?? inferWatermarkSubtitle(table));
  const watermarkDate = options?.watermarkDate ?? new Date().toLocaleDateString("es-AR");
  const savedPreset = await getSavedWatermarkPreset();
  const effectivePreset = {
    opacity: options?.watermarkOpacity ?? savedPreset.opacity ?? DEFAULT_WATERMARK_PRESET.opacity,
    offsetX: options?.watermarkOffsetX ?? savedPreset.offsetX ?? DEFAULT_WATERMARK_PRESET.offsetX,
    offsetY: options?.watermarkOffsetY ?? savedPreset.offsetY ?? DEFAULT_WATERMARK_PRESET.offsetY,
    stampGapX: options?.watermarkStampGapX ?? savedPreset.stampGapX ?? DEFAULT_WATERMARK_PRESET.stampGapX,
    stampGapY: options?.watermarkStampGapY ?? savedPreset.stampGapY ?? DEFAULT_WATERMARK_PRESET.stampGapY,
    fontFamily: options?.watermarkFontFamily ?? savedPreset.fontFamily ?? DEFAULT_WATERMARK_PRESET.fontFamily,
  };

  function cellColor(header: string, cell: string): string {
    const amount = parseAmount(cell);

    if (amount !== null && amount < 0) return "#dc2626";
    if (amount !== null && isDebtColumn(header)) return amount > 0 ? "#dc2626" : "#334155";
    if (amount !== null && isPaymentColumn(header)) return amount > 0 ? "#16a34a" : "#334155";
    return "#334155";
  }

  const rawWidths = table.headers.map((header, index) => {
    return estimateColumnWidth(header, table.rows.map((row) => row[index] ?? ""));
  });
  const colWidths = distributeExtraWidth(
    rawWidths,
    (isCompactDebtSummary ? COMPACT_TABLE_WIDTH : MIN_TABLE_WIDTH) - OUTER_PADDING * 2
  );
  const tableWidth = colWidths.reduce((sum, width) => sum + width, 0);
  const headerHeight = estimateRowHeight(table.headers, colWidths);
  const rowHeights = table.rows.map((row) => estimateRowHeight(row, colWidths));
  const totalWidth = tableWidth + OUTER_PADDING * 2;
  const totalHeight = OUTER_PADDING * 2 + headerHeight + rowHeights.reduce((sum, height) => sum + height, 0);
  // Build React element tree for satori
  const element = React.createElement(
    "div",
    {
      style: {
        display: "flex",
        flexDirection: "column",
        position: "relative",
        width: totalWidth,
        height: totalHeight,
        backgroundColor: "#ffffff",
        fontFamily: "Geist",
      },
    },
    React.createElement(
      "div",
      {
        style: {
          position: "relative",
          zIndex: 1,
          display: "flex",
          flexDirection: "column",
          width: totalWidth,
          height: totalHeight,
          padding: OUTER_PADDING,
        },
      },
      React.createElement(
        "div",
        {
          style: {
            display: "flex",
            flexDirection: "column",
          width: tableWidth,
          borderRadius: 8,
          overflow: "hidden",
          border: "1px solid #cbd5e1",
        },
      },
      React.createElement(
        "div",
        {
          style: {
            display: "flex",
            backgroundColor: "#1e293b",
            minHeight: headerHeight,
          },
        },
        ...table.headers.map((header, index) =>
          React.createElement(
            "div",
            {
              key: `h${index}`,
              style: {
                width: colWidths[index],
                minWidth: colWidths[index],
                maxWidth: colWidths[index],
                flexGrow: 0,
                flexShrink: 0,
                paddingLeft: CELL_HORIZONTAL_PADDING,
                paddingRight: CELL_HORIZONTAL_PADDING,
                paddingTop: CELL_VERTICAL_PADDING,
                paddingBottom: CELL_VERTICAL_PADDING,
                fontSize: FONT_SIZE,
                fontWeight: 700,
                lineHeight: 1.4,
                color: "#ffffff",
                textAlign: isNumericColumn(header) ? "right" : "left",
                display: "flex",
                alignItems: "center",
                justifyContent: isNumericColumn(header) ? "flex-end" : "flex-start",
                whiteSpace: "pre-wrap",
              },
            },
            header
          )
        )
      ),
      ...table.rows.map((row, rowIndex) =>
        React.createElement(
          "div",
          {
            key: `r${rowIndex}`,
            style: {
              display: "flex",
              backgroundColor: isTotalRow(row) ? "#e2e8f0" : rowIndex % 2 === 0 ? "#f8fafc" : "#ffffff",
              minHeight: rowHeights[rowIndex],
              borderTop: isTotalRow(row) ? "2px solid #94a3b8" : "1px solid #e2e8f0",
            },
          },
          ...row.map((cell, cellIndex) =>
            React.createElement(
              "div",
              {
                key: `c${cellIndex}`,
                style: {
                  width: colWidths[cellIndex],
                  minWidth: colWidths[cellIndex],
                  maxWidth: colWidths[cellIndex],
                  flexGrow: 0,
                  flexShrink: 0,
                  paddingLeft: CELL_HORIZONTAL_PADDING,
                  paddingRight: CELL_HORIZONTAL_PADDING,
                  paddingTop: CELL_VERTICAL_PADDING,
                  paddingBottom: CELL_VERTICAL_PADDING,
                  fontSize: isTotalRow(row) ? FONT_SIZE + 2 : FONT_SIZE,
                  fontWeight: isTotalRow(row) ? 900 : 400,
                  lineHeight: 1.4,
                  color: isTotalRow(row) && cellIndex === 0 ? "#0f172a" : cellColor(table.headers[cellIndex], cell),
                  textAlign: isNumericColumn(table.headers[cellIndex]) ? "right" : "left",
                  display: "flex",
                  alignItems: "center",
                  justifyContent: isNumericColumn(table.headers[cellIndex]) ? "flex-end" : "flex-start",
                  whiteSpace: "pre-wrap",
                },
              },
              cell || " "
            )
          )
        )
        )
      )
    )
  );

  try {
    const svg = await satori(element, {
      width: totalWidth,
      height: totalHeight,
      fonts: [
        {
          name: "Geist",
          data: getFont(),
          weight: 400,
          style: "normal",
        },
      ],
    });

    const basePng = await sharp(Buffer.from(svg)).png().toBuffer();
    const overlayPng = await renderWatermarkOverlayPng(totalWidth, totalHeight, WATERMARK_TEXT, watermarkSubtitle, watermarkDate, {
      opacity: effectivePreset.opacity,
      offsetX: effectivePreset.offsetX,
      offsetY: effectivePreset.offsetY,
      stampGapX: effectivePreset.stampGapX,
      stampGapY: effectivePreset.stampGapY,
      fontFamily: effectivePreset.fontFamily,
    });

    return await sharp(basePng)
      .composite([{ input: overlayPng }])
      .png()
      .toBuffer();
  } catch (error) {
    console.error("[TABLE] renderTableToImage failed:", error);
    return null;
  }
}

/**
 * Extract all tables from a message and render them.
 * The text field is always populated — for image segments it holds the original
 * markdown so callers can fall back to text when image delivery fails.
 */
export async function extractAndRenderTables(message: string): Promise<{ text: string; image: Buffer | null }[]> {
  const segments: { text: string; image: Buffer | null }[] = [];

  let remaining = message;
  while (remaining.trim()) {
    const { table, before, after, rawTable } = parseMarkdownTable(remaining);

    if (!table) {
      segments.push({ text: remaining, image: null });
      break;
    }

    if (before) segments.push({ text: before, image: null });
    segments.push({ text: rawTable, image: await renderTableToImage(table) });

    if (!after) break;
    remaining = after;
  }

  if (!segments.length) segments.push({ text: message, image: null });

  return segments;
}
