const crypto = require("crypto");
const { db } = require("./db");
const { publishWebEvent } = require("./web-events");
const { getOverlayModuleType } = require("./overlay-modules");

const recentByOverlay = new Map();
const MAX_RECENT_PER_OVERLAY = 100;
const MAX_RECENT_AGE_MS = 5 * 60 * 1000;

function cleanText(value, max = 500) {
  return String(value === undefined || value === null ? "" : value).trim().slice(0, max);
}

function cleanMessageText(value, max = 2000) {
  return String(value === undefined || value === null ? "" : value).slice(0, max);
}

function cleanUrl(value) {
  const candidate = cleanText(value, 2048);
  if (!candidate) return null;
  try {
    const parsed = new URL(candidate);
    return ["http:", "https:"].includes(parsed.protocol) ? parsed.toString() : null;
  } catch {
    return null;
  }
}

function normalizeBadge(badge) {
  if (typeof badge === "string") {
    const label = cleanText(badge, 40);
    return label ? { label, image: null } : null;
  }
  const label = cleanText(badge?.label || badge?.name, 40);
  if (!label) return null;
  return { label, image: cleanUrl(badge?.image || badge?.url) };
}

function normalizeEmote(emote) {
  const parsedStart = Number(emote?.start);
  const parsedEnd = Number(emote?.end);
  if (!Number.isFinite(parsedStart) || !Number.isFinite(parsedEnd)) return null;
  const start = Math.max(0, Math.round(parsedStart));
  const end = Math.max(start, Math.round(parsedEnd));
  const image = cleanUrl(emote?.image || emote?.url);
  return image ? { start, end, image, label: cleanText(emote?.label, 100) } : null;
}

function normalizeMedia(media) {
  const url = cleanUrl(media?.url);
  if (!url) return null;
  return {
    url,
    type: media?.type === "video" ? "video" : "image",
    alt: cleanText(media?.alt || "Animated image", 160)
  };
}

function normalizeChatMessage(input = {}) {
  const platform = cleanText(input.platform, 24).toLowerCase();
  const text = cleanMessageText(input.text, 2000);
  const authorId = cleanText(input.author?.id, 160);
  const media = (Array.isArray(input.media) ? input.media : []).map(normalizeMedia).filter(Boolean).slice(0, 4);
  if (!platform || (!text.trim() && !media.length) || !authorId) return null;
  const channelId = cleanText(input.channel?.id, 256);
  const channelName = cleanText(input.channel?.name, 256);
  const channelKey = cleanText(input.channel?.key || channelName || channelId, 256);
  return {
    id: cleanText(input.id, 256) || crypto.randomUUID(),
    platform,
    text,
    timestamp: Number.isFinite(Number(input.timestamp)) ? Number(input.timestamp) : Date.now(),
    channel: { id: channelId, name: channelName, key: channelKey },
    emotes: (Array.isArray(input.emotes) ? input.emotes : []).map(normalizeEmote).filter(Boolean).slice(0, 100),
    media,
    author: {
      id: authorId,
      name: cleanText(input.author?.name || input.author?.username || "Viewer", 160),
      username: cleanText(input.author?.username, 160),
      avatar: cleanUrl(input.author?.avatar),
      color: /^#[0-9a-f]{6}$/i.test(cleanText(input.author?.color, 7)) ? cleanText(input.author.color, 7) : null,
      badges: (Array.isArray(input.author?.badges) ? input.author.badges : []).map(normalizeBadge).filter(Boolean).slice(0, 12),
      lumi: input.author?.lumi?.id ? {
        id: cleanText(input.author.lumi.id, 160),
        username: cleanText(input.author.lumi.username, 160)
      } : null
    }
  };
}

function normalizedChannel(value) {
  return String(value || "").trim().toLowerCase().replace(/^#/, "");
}

function messageMatchesConfig(message, config = {}) {
  const platforms = Array.isArray(config.platforms) ? config.platforms : ["twitch", "youtube", "discord"];
  if (!platforms.includes(message.platform) || messageBlockedByConfig(message, config)) return false;
  const filters = Array.isArray(config.channels) ? config.channels : [];
  if (!filters.length) return true;
  const aliases = [message.channel?.id, message.channel?.name, message.channel?.key].map(normalizedChannel).filter(Boolean);
  return filters.some((entry) => {
    const raw = String(entry || "").trim();
    const separator = raw.indexOf(":");
    const platform = separator > 0 ? raw.slice(0, separator).trim().toLowerCase() : "";
    const channel = normalizedChannel(separator > 0 ? raw.slice(separator + 1) : raw);
    return (!platform || platform === message.platform) && (channel === "*" || aliases.includes(channel));
  });
}

function messageBlockedByConfig(message, config = {}) {
  const rules = Array.isArray(config.blockedUsers) ? config.blockedUsers : [];
  return rules.some((entry) => {
    const raw = String(entry || "").trim();
    const separator = raw.indexOf(":");
    if (separator < 1) return false;
    const platform = raw.slice(0, separator).toLowerCase();
    const values = raw.slice(separator + 1).split("|").map((value) => normalizedIdentity(value)).filter(Boolean);
    const aliases = platform === "lumi"
      ? [message.author?.lumi?.id, message.author?.lumi?.username]
      : platform === message.platform
        ? [message.author?.id, message.author?.username, message.author?.name]
        : [];
    const normalizedAliases = aliases.map(normalizedIdentity).filter(Boolean);
    return values.some((value) => normalizedAliases.includes(value));
  });
}

function normalizedIdentity(value) {
  return String(value || "").trim().toLowerCase().replace(/^@/, "");
}

function overlayTargets(message) {
  const rows = db.prepare(
    `SELECT s.overlay_id, m.type, m.config_json
       FROM overlay_modules m
       JOIN overlay_scenes s ON s.id = m.scene_id
       JOIN overlays o ON o.id = s.overlay_id
      WHERE m.enabled = 1 AND s.enabled = 1 AND o.enabled = 1`
  ).all();
  const targets = new Set();
  for (const row of rows) {
    if (getOverlayModuleType(row.type)?.renderType !== "chat") continue;
    let config = {};
    try { config = JSON.parse(row.config_json || "{}"); } catch {}
    if (messageMatchesConfig(message, config)) targets.add(row.overlay_id);
  }
  return [...targets];
}

function remember(overlayId, message) {
  const cutoff = Date.now() - MAX_RECENT_AGE_MS;
  const recent = (recentByOverlay.get(overlayId) || []).filter((entry) => entry.timestamp >= cutoff && entry.id !== message.id);
  recent.push(message);
  recentByOverlay.set(overlayId, recent.slice(-MAX_RECENT_PER_OVERLAY));
}

function publishOverlayChatMessage(input = {}) {
  const message = normalizeChatMessage(input);
  if (!message) return 0;
  let delivered = 0;
  for (const overlayId of overlayTargets(message)) {
    remember(overlayId, message);
    delivered += publishWebEvent("overlay:chat-message", message, { scope: `overlay:${overlayId}` });
  }
  return delivered;
}

function recentOverlayChatMessages(overlayId) {
  const cutoff = Date.now() - MAX_RECENT_AGE_MS;
  const recent = (recentByOverlay.get(String(overlayId)) || []).filter((entry) => entry.timestamp >= cutoff);
  recentByOverlay.set(String(overlayId), recent);
  return recent.map((payload) => ({ event: "overlay:chat-message", payload }));
}

module.exports = {
  messageBlockedByConfig,
  messageMatchesConfig,
  normalizeChatMessage,
  publishOverlayChatMessage,
  recentOverlayChatMessages
};
