import "server-only";

import { randomUUID } from "crypto";
import path from "path";
import sanitizeHtml from "sanitize-html";
import type { RowDataPacket } from "mysql2/promise";
import { ensureOfficialMailSchema, getDbPool } from "@/lib/db";
import { sanitizeMailboxHtml } from "@/lib/mail-html";
import {
  deleteObjectFromObjectStorage,
  uploadBufferToObjectStorage,
} from "@/lib/object-storage";

export type MailSignatureSettings = {
  enabled: boolean;
  html: string;
  imageUrl: string | null;
  imageWidth: number;
  text: string;
};

type MailSignatureRow = RowDataPacket & {
  id: number;
  mail_signature_enabled: number;
  mail_signature_html: string | null;
  mail_signature_image_key: string | null;
  mail_signature_image_url: string | null;
  mail_signature_image_width: number;
  mail_signature_text: string | null;
};

const DEFAULT_SIGNATURE_IMAGE_WIDTH = 420;
const MAX_SIGNATURE_HTML_LENGTH = 50_000;
const MAX_SIGNATURE_IMAGE_BYTES = 5 * 1024 * 1024;
const MAX_SIGNATURE_TEXT_LENGTH = 10_000;
const SIGNATURE_IMAGE_CONTENT_TYPES = new Map([
  ["image/gif", "gif"],
  ["image/jpeg", "jpg"],
  ["image/png", "png"],
  ["image/webp", "webp"],
]);

function normalizeSignatureImageWidth(value: unknown) {
  const width = Number(value);

  if (!Number.isFinite(width)) {
    return DEFAULT_SIGNATURE_IMAGE_WIDTH;
  }

  return Math.max(240, Math.min(720, Math.round(width)));
}

function signatureHtmlToText(value: string) {
  return sanitizeHtml(
    value
      .replace(/<br\s*\/?>/gi, "\n")
      .replace(/<\/(?:div|h[1-6]|li|p|tr)>/gi, "\n"),
    {
      allowedAttributes: {},
      allowedTags: [],
    },
  )
    .replace(/\u00a0/g, " ")
    .replace(/[ \t]+\n/g, "\n")
    .replace(/\n{3,}/g, "\n\n")
    .trim()
    .slice(0, MAX_SIGNATURE_TEXT_LENGTH);
}

function normalizeSignatureHtml(value: unknown) {
  const source = typeof value === "string" ? value.trim() : "";

  if (source.length > MAX_SIGNATURE_HTML_LENGTH) {
    throw new Error("signature-too-long");
  }

  // Images are stored separately so arbitrary tracking pixels cannot be saved in a signature.
  const withoutImages = source.replace(/<img\b[^>]*>/gi, "");
  const html = sanitizeMailboxHtml(withoutImages) ?? "";

  return {
    html,
    text: signatureHtmlToText(html),
  };
}

async function getMailSignatureRowByEmail(email: string) {
  await ensureOfficialMailSchema();
  const [rows] = await getDbPool().query<MailSignatureRow[]>(
    `
      SELECT
        id,
        mail_signature_enabled,
        mail_signature_html,
        mail_signature_text,
        mail_signature_image_url,
        mail_signature_image_key,
        mail_signature_image_width
      FROM users
      WHERE LOWER(email) = ?
      LIMIT 1
    `,
    [email.trim().toLowerCase()],
  );

  return rows[0] ?? null;
}

function mapMailSignatureRow(row: MailSignatureRow): MailSignatureSettings {
  return {
    enabled: Boolean(row.mail_signature_enabled),
    html: row.mail_signature_html?.trim() ?? "",
    imageUrl: row.mail_signature_image_url?.trim() || null,
    imageWidth: normalizeSignatureImageWidth(row.mail_signature_image_width),
    text: row.mail_signature_text?.trim() ?? "",
  };
}

export async function getMailSignatureByEmail(email: string) {
  const row = await getMailSignatureRowByEmail(email);

  if (!row) {
    throw new Error("user-not-found");
  }

  return mapMailSignatureRow(row);
}

export async function updateMailSignatureByEmail(input: {
  email: string;
  enabled: boolean;
  html: unknown;
  imageWidth: unknown;
}) {
  const row = await getMailSignatureRowByEmail(input.email);

  if (!row) {
    throw new Error("user-not-found");
  }

  const normalized = normalizeSignatureHtml(input.html);
  const imageWidth = normalizeSignatureImageWidth(input.imageWidth);
  const hasContent = Boolean(normalized.text || row.mail_signature_image_url);

  if (input.enabled && !hasContent) {
    throw new Error("signature-empty");
  }

  await getDbPool().query(
    `
      UPDATE users
      SET
        mail_signature_enabled = ?,
        mail_signature_html = ?,
        mail_signature_text = ?,
        mail_signature_image_width = ?,
        updated_at = NOW()
      WHERE id = ?
    `,
    [input.enabled ? 1 : 0, normalized.html || null, normalized.text || null, imageWidth, row.id],
  );

  return getMailSignatureByEmail(input.email);
}

export async function uploadMailSignatureImageByEmail(input: {
  body: Buffer;
  contentType: string;
  email: string;
  filename: string;
}) {
  const row = await getMailSignatureRowByEmail(input.email);

  if (!row) {
    throw new Error("user-not-found");
  }

  if (input.body.length === 0) {
    throw new Error("signature-image-empty");
  }

  if (input.body.length > MAX_SIGNATURE_IMAGE_BYTES) {
    throw new Error("signature-image-too-large");
  }

  const contentType = input.contentType.trim().toLowerCase();
  const extension = SIGNATURE_IMAGE_CONTENT_TYPES.get(contentType);

  if (!extension) {
    throw new Error("signature-image-invalid");
  }

  const storageKey = path.posix.join(
    "mail-signatures",
    String(row.id),
    `${Date.now()}-${randomUUID()}.${extension}`,
  );
  const uploaded = await uploadBufferToObjectStorage({
    body: input.body,
    cacheControl: "public, max-age=31536000, immutable",
    contentType,
    key: storageKey,
  });

  try {
    await getDbPool().query(
      `
        UPDATE users
        SET
          mail_signature_image_url = ?,
          mail_signature_image_key = ?,
          updated_at = NOW()
        WHERE id = ?
      `,
      [uploaded.publicUrl, uploaded.key, row.id],
    );
  } catch (error) {
    await deleteObjectFromObjectStorage({ key: uploaded.key }).catch(() => undefined);
    throw error;
  }

  if (row.mail_signature_image_key && row.mail_signature_image_key !== uploaded.key) {
    await deleteObjectFromObjectStorage({ key: row.mail_signature_image_key }).catch(() => undefined);
  }

  return getMailSignatureByEmail(input.email);
}

export async function removeMailSignatureImageByEmail(email: string) {
  const row = await getMailSignatureRowByEmail(email);

  if (!row) {
    throw new Error("user-not-found");
  }

  await getDbPool().query(
    `
      UPDATE users
      SET
        mail_signature_image_url = NULL,
        mail_signature_image_key = NULL,
        mail_signature_enabled = CASE
          WHEN NULLIF(TRIM(mail_signature_text), '') IS NULL THEN 0
          ELSE mail_signature_enabled
        END,
        updated_at = NOW()
      WHERE id = ?
    `,
    [row.id],
  );

  if (row.mail_signature_image_key) {
    await deleteObjectFromObjectStorage({ key: row.mail_signature_image_key }).catch(() => undefined);
  }

  return getMailSignatureByEmail(email);
}

export function buildMailSignatureComposeContent(settings: MailSignatureSettings) {
  if (!settings.enabled || (!settings.html && !settings.imageUrl)) {
    return { html: "", text: "" };
  }

  const imageHtml = settings.imageUrl
    ? `<div style="margin-top:${settings.html ? "12px" : "0"}"><img src="${sanitizeHtml(settings.imageUrl, { allowedAttributes: {}, allowedTags: [] })}" alt="메일 서명" width="${settings.imageWidth}" style="display:block;width:${settings.imageWidth}px;max-width:100%;height:auto;border:0" /></div>`
    : "";
  const html = sanitizeMailboxHtml(
    `<div data-official-mail-signature="true" style="margin-top:18px">${settings.html}${imageHtml}</div>`,
  ) ?? "";

  return {
    html,
    text: settings.text,
  };
}
