const crypto = require("crypto");
const { db } = require("./db");
const { createLogger } = require("./logger");
const { getOverlayModuleType } = require("./overlay-modules");
const { emitLumiEvent, listEventTypes, onLumiEvent, registerEventType } = require("./lumi-events");
const { publishWebEvent } = require("./web-events");

const hookLog = createLogger("core:overlay-events", { category: "automation" });
const recentEventIds = new Map();
const EVENT_DEDUP_MS = 10 * 60 * 1000;
const ACTION_TYPES = Object.freeze([
  { id: "play_once", label: "Play media once", description: "Restart an audio or video source and play it once.", renderTypes: ["audio", "video"] },
  { id: "show_temporary", label: "Show source temporarily", description: "Show a text or image source for the selected time, then hide it again.", renderTypes: ["text", "image"] }
]);

function parseFilters(value) {
  try {
    const parsed = typeof value === "string" ? JSON.parse(value || "{}") : value;
    return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
  } catch {
    return {};
  }
}

function actionDefinition(id) {
  return ACTION_TYPES.find((action) => action.id === id) || null;
}

function eventDefinition(id) {
  return listEventTypes().find((event) => event.id === id) || null;
}

function normalizedTier(value) {
  const tier = String(value || "").trim();
  return ["1000", "2000", "3000"].includes(tier) ? tier : "";
}

function normalizedDuration(value) {
  const duration = Number(value);
  return Number.isFinite(duration) ? Math.min(3600, Math.max(0.5, duration)) : 5;
}

function moduleRecord(overlayId, moduleId) {
  return db.prepare(
    `SELECT m.id, m.type, m.name, m.enabled, s.id AS scene_id, s.name AS scene_name, s.enabled AS scene_enabled, s.overlay_id
     FROM overlay_modules m JOIN overlay_scenes s ON s.id = m.scene_id
     WHERE m.id = ? AND s.overlay_id = ?`
  ).get(moduleId, overlayId);
}

function validateHookInput(input) {
  const overlayId = String(input.overlayId || "");
  const eventType = String(input.eventType || "").trim().toLowerCase();
  const actionType = String(input.actionType || "").trim().toLowerCase();
  const moduleId = String(input.moduleId || "");
  const event = eventDefinition(eventType);
  const action = actionDefinition(actionType);
  const module = moduleRecord(overlayId, moduleId);
  if (!event) throw new Error("Choose an available event.");
  if (!action) throw new Error("Choose an available alert action.");
  if (!module) throw new Error("Choose a source from this overlay.");
  if (!module.enabled || !module.scene_enabled) throw new Error("The selected source and its scene must be enabled.");
  const renderType = getOverlayModuleType(module.type)?.renderType || module.type;
  if (!action.renderTypes.includes(renderType)) {
    throw new Error(`${action.label} requires ${action.renderTypes.join(" or ")} source.`);
  }
  const tier = event.supportsTier ? normalizedTier(input.tier) : "";
  return {
    overlayId,
    eventType,
    actionType,
    moduleId,
    durationSeconds: actionType === "show_temporary" ? normalizedDuration(input.durationSeconds) : 0,
    filters: tier ? { tier } : {},
    enabled: input.enabled !== false,
    module
  };
}

function rowPresentation(row) {
  return {
    ...row,
    enabled: Boolean(row.enabled),
    duration_seconds: Number(row.duration_seconds || 0),
    filters: parseFilters(row.filters_json),
    event: eventDefinition(row.event_type),
    action: actionDefinition(row.action_type)
  };
}

function listOverlayEventHooks(overlayId) {
  return db.prepare(
    `SELECT h.*, m.name AS module_name, m.type AS module_type, s.name AS scene_name
     FROM overlay_event_hooks h
     JOIN overlay_modules m ON m.id = h.module_id
     JOIN overlay_scenes s ON s.id = m.scene_id
     WHERE h.overlay_id = ? ORDER BY h.created_at, h.id`
  ).all(overlayId).map(rowPresentation);
}

function touchOverlay(overlayId) {
  const now = Date.now();
  const changed = db.prepare("UPDATE overlays SET updated_at = ? WHERE id = ?").run(now, overlayId);
  if (changed.changes) publishWebEvent("overlay:changed", { revision: now }, { scope: `overlay:${overlayId}` });
}

function createOverlayEventHook(input) {
  const value = validateHookInput(input);
  const id = crypto.randomUUID();
  const now = Date.now();
  db.prepare(
    `INSERT INTO overlay_event_hooks
      (id, overlay_id, event_type, action_type, module_id, duration_seconds, filters_json, enabled, created_at, updated_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
  ).run(id, value.overlayId, value.eventType, value.actionType, value.moduleId, value.durationSeconds, JSON.stringify(value.filters), value.enabled ? 1 : 0, now, now);
  touchOverlay(value.overlayId);
  return listOverlayEventHooks(value.overlayId).find((hook) => hook.id === id);
}

function updateOverlayEventHook(id, input) {
  const existing = db.prepare("SELECT * FROM overlay_event_hooks WHERE id = ? AND overlay_id = ?").get(id, input.overlayId);
  if (!existing) throw new Error("Event alert not found.");
  const value = validateHookInput(input);
  db.prepare(
    `UPDATE overlay_event_hooks SET event_type = ?, action_type = ?, module_id = ?, duration_seconds = ?, filters_json = ?, enabled = ?, updated_at = ?
     WHERE id = ?`
  ).run(value.eventType, value.actionType, value.moduleId, value.durationSeconds, JSON.stringify(value.filters), value.enabled ? 1 : 0, Date.now(), id);
  touchOverlay(value.overlayId);
  return listOverlayEventHooks(value.overlayId).find((hook) => hook.id === id);
}

function deleteOverlayEventHook(id, overlayId) {
  const result = db.prepare("DELETE FROM overlay_event_hooks WHERE id = ? AND overlay_id = ?").run(id, overlayId);
  if (!result.changes) throw new Error("Event alert not found.");
  touchOverlay(overlayId);
}

function matchesFilters(filters, payload) {
  if (filters.tier && String(payload?.tier || "") !== filters.tier) return false;
  return true;
}

function publishHookAction(hook, event) {
  return publishWebEvent("overlay:action", {
    action_id: crypto.randomUUID(),
    hook_id: hook.id,
    module_id: hook.module_id,
    action: hook.action_type,
    duration_seconds: Number(hook.duration_seconds || 0),
    event_type: event.type,
    test: Boolean(event.test)
  }, { scope: `overlay:${hook.overlay_id}` });
}

function eventWasHandled(event) {
  const now = Date.now();
  for (const [id, at] of recentEventIds) if (now - at > EVENT_DEDUP_MS) recentEventIds.delete(id);
  const key = `${event.source}:${event.id}`;
  if (recentEventIds.has(key)) return true;
  recentEventIds.set(key, now);
  return false;
}

function dispatchOverlayEvent(event) {
  if (eventWasHandled(event)) return { matched: 0, delivered: 0, duplicate: true };
  const hooks = db.prepare(
    `SELECT h.* FROM overlay_event_hooks h
     JOIN overlays o ON o.id = h.overlay_id
     JOIN overlay_modules m ON m.id = h.module_id
     JOIN overlay_scenes s ON s.id = m.scene_id
     WHERE h.event_type = ? AND h.enabled = 1 AND o.enabled = 1 AND m.enabled = 1 AND s.enabled = 1`
  ).all(event.type).filter((hook) => matchesFilters(parseFilters(hook.filters_json), event.payload));
  let delivered = 0;
  for (const hook of hooks) delivered += publishHookAction(hook, event);
  if (hooks.length) hookLog.info("Overlay event alert dispatched", {
    event_type: event.type,
    matched_hooks: hooks.length,
    browser_sources_reached: delivered,
    test: event.test
  }, { event: "overlay_event_dispatched" });
  return { matched: hooks.length, delivered, duplicate: false };
}

function testOverlayEventHook(id, overlayId) {
  const hook = db.prepare("SELECT * FROM overlay_event_hooks WHERE id = ? AND overlay_id = ?").get(id, overlayId);
  if (!hook) throw new Error("Event alert not found.");
  const event = { id: crypto.randomUUID(), type: hook.event_type, payload: parseFilters(hook.filters_json), source: "admin-test", test: true };
  return { hook: rowPresentation(hook), delivered: publishHookAction(hook, event) };
}

function eventOnlyModuleIds(sceneId) {
  return new Set(db.prepare(
    `SELECT DISTINCT h.module_id FROM overlay_event_hooks h
     JOIN overlay_modules m ON m.id = h.module_id
     WHERE m.scene_id = ? AND h.enabled = 1 AND h.action_type = 'show_temporary'`
  ).all(sceneId).map((row) => row.module_id));
}

function duplicateOverlayEventHooks(sourceOverlayId, targetOverlayId, moduleIds) {
  const hooks = db.prepare("SELECT * FROM overlay_event_hooks WHERE overlay_id = ?").all(sourceOverlayId);
  const insert = db.prepare(
    `INSERT INTO overlay_event_hooks
      (id, overlay_id, event_type, action_type, module_id, duration_seconds, filters_json, enabled, created_at, updated_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
  );
  const now = Date.now();
  for (const hook of hooks) {
    const moduleId = moduleIds.get(hook.module_id);
    if (moduleId) insert.run(crypto.randomUUID(), targetOverlayId, hook.event_type, hook.action_type, moduleId, hook.duration_seconds, hook.filters_json, hook.enabled, now, now);
  }
}

function eventHooksApi() {
  return Object.freeze({ emit: emitLumiEvent, listTypes: listEventTypes, on: onLumiEvent, registerType: registerEventType });
}

onLumiEvent(dispatchOverlayEvent);

module.exports = {
  ACTION_TYPES,
  createOverlayEventHook,
  deleteOverlayEventHook,
  dispatchOverlayEvent,
  duplicateOverlayEventHooks,
  eventHooksApi,
  eventOnlyModuleIds,
  listOverlayEventHooks,
  testOverlayEventHook,
  updateOverlayEventHook
};
