import express from "express";
import { z } from "zod";
import { authenticate, requireMfa } from "../../middleware/auth.js";
import { requirePermission, requireRoles } from "../../middleware/rbac.js";
import { validate } from "../../middleware/validate.js";
import { withDbSession } from "../../config/db.js";
import { writeAudit } from "../../utils/audit.js";
import { upload } from "../../middleware/upload.js";
import { buildIcsCalendar, normalizeImportedEvent, parseIcsCalendar } from "./ics.js";

const router = express.Router();
router.use(authenticate);

const priorityEnum = z.enum(["sehr_hoch", "hoch", "mittel", "niedrig"]);
const visibilityEnum = z.enum(["intern", "vertraulich", "streng_vertraulich"]);

const eventSchema = z.object({
  title: z.string().min(2).max(255),
  description: z.string().max(8000).nullable().optional(),
  starts_at: z.string().min(10),
  ends_at: z.string().min(10).nullable().optional(),
  all_day: z.boolean().optional().default(false),
  location: z.string().max(255).nullable().optional(),
  status: z.string().min(2).max(30).optional().default("geplant"),
  priority: priorityEnum.optional().default("mittel"),
  visibility: visibilityEnum.optional().default("intern"),
  owner_user_id: z.number().int().positive().nullable().optional(),
  committee_id: z.number().int().positive().nullable().optional(),
  source_name: z.string().max(255).nullable().optional(),
});

const updateEventSchema = eventSchema.partial();

function normalizeRange(from, to) {
  const start = from ? new Date(from) : new Date();
  const startIso = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate(), 0, 0, 0)).toISOString();
  const end = to ? new Date(to) : new Date(new Date(startIso).getTime() + 1000 * 60 * 60 * 24 * 31);
  const endIso = new Date(Date.UTC(end.getUTCFullYear(), end.getUTCMonth(), end.getUTCDate(), 23, 59, 59)).toISOString();
  return { startIso, endIso };
}

function normalizeNullableText(value) {
  if (value === undefined) return undefined;
  if (value === null) return null;
  const trimmed = String(value).trim();
  return trimmed ? trimmed : null;
}

function normalizeNullableNumber(value) {
  if (value === undefined) return undefined;
  if (value === null || value === "") return null;
  return Number(value);
}

async function assertReference(db, table, id, publicMessage) {
  if (id === undefined || id === null) return;
  const result = await db.query(`SELECT id FROM ${table} WHERE id = $1 LIMIT 1`, [id]);
  if (!result.rows[0]) {
    throw Object.assign(new Error(publicMessage), { status: 400, publicMessage });
  }
}

async function assertEventReferences(db, payload) {
  await assertReference(db, "committee", payload.committee_id, "Ausschuss nicht gefunden");
  if (payload.owner_user_id !== undefined && payload.owner_user_id !== null) {
    const result = await db.query(`SELECT id FROM app_user WHERE id = $1 AND is_active = TRUE`, [payload.owner_user_id]);
    if (!result.rows[0]) {
      throw Object.assign(new Error("Benutzer nicht gefunden"), { status: 400, publicMessage: "Benutzer nicht gefunden" });
    }
  }
}

function mapEventRow(row) {
  return {
    id: row.id,
    event_type: row.event_type,
    title: row.title,
    reference: row.reference,
    status: row.status,
    event_date: row.event_date,
    starts_at: row.starts_at,
    ends_at: row.ends_at,
    location: row.location,
    committee_id: row.committee_id,
    committee_name: row.committee_name,
    owner_user_id: row.owner_user_id,
    owner_name: row.owner_name,
    primary_user_id: row.primary_user_id,
    primary_user_name: row.primary_user_name,
    secondary_user_id: row.secondary_user_id,
    secondary_user_name: row.secondary_user_name,
    priority: row.priority,
    linked_case_number: row.linked_case_number,
    description: row.description,
    all_day: Boolean(row.all_day),
    visibility: row.visibility,
    source_type: row.source_type,
    source_name: row.source_name,
    editable: Boolean(row.editable),
    created_at: row.created_at,
    updated_at: row.updated_at,
    ical_uid: row.ical_uid,
  };
}

async function listEvents(db, { from, to, committeeId, userId }) {
  const { startIso, endIso } = normalizeRange(from, to);
  const params = [startIso, endIso];
  const push = (value) => {
    params.push(value);
    return `$${params.length}`;
  };

  let meetingWhere = `m.meeting_date::timestamptz BETWEEN $1 AND $2`;
  let deadlineWhere = `d.due_date BETWEEN $1 AND $2`;
  let taskWhere = `t.due_date IS NOT NULL AND t.due_date BETWEEN $1 AND $2`;
  let calendarWhere = `e.deleted_at IS NULL AND e.starts_at <= $2 AND COALESCE(e.ends_at, e.starts_at) >= $1`;

  if (committeeId) {
    const p = push(committeeId);
    meetingWhere += ` AND m.committee_id = ${p}`;
    deadlineWhere += ` AND c.committee_id = ${p}`;
    taskWhere += ` AND COALESCE(tm.committee_id, tc.committee_id) = ${p}`;
    calendarWhere += ` AND e.committee_id = ${p}`;
  }

  if (userId) {
    const p = push(userId);
    meetingWhere += ` AND (m.chair_user_id = ${p} OR m.secretary_user_id = ${p})`;
    deadlineWhere += ` AND c.owner_user_id = ${p}`;
    taskWhere += ` AND (t.assigned_user_id = ${p} OR t.backup_user_id = ${p})`;
    calendarWhere += ` AND e.owner_user_id = ${p}`;
  }

  const [meetings, deadlines, tasks, calendarItems] = await Promise.all([
    db.query(
      `SELECT m.id,
              'meeting' AS event_type,
              m.title,
              m.meeting_number AS reference,
              m.status,
              m.meeting_date::text AS event_date,
              (m.meeting_date::text || 'T' || COALESCE(m.start_time::text, '00:00:00'))::timestamptz AS starts_at,
              CASE WHEN m.end_time IS NOT NULL THEN (m.meeting_date::text || 'T' || m.end_time::text)::timestamptz ELSE NULL END AS ends_at,
              m.location,
              m.committee_id,
              c.name AS committee_name,
              NULL::bigint AS owner_user_id,
              NULL::text AS owner_name,
              m.chair_user_id AS primary_user_id,
              cu.full_name AS primary_user_name,
              m.secretary_user_id AS secondary_user_id,
              su.full_name AS secondary_user_name,
              NULL::text AS priority,
              NULL::text AS linked_case_number,
              m.protocol_text AS description,
              FALSE AS all_day,
              'intern'::text AS visibility,
              'meeting'::text AS source_type,
              NULL::text AS source_name,
              FALSE AS editable,
              m.created_at,
              m.updated_at,
              ('meeting-' || m.id || '@br-office.local')::text AS ical_uid
         FROM meeting m
         LEFT JOIN committee c ON c.id = m.committee_id
         LEFT JOIN app_user cu ON cu.id = m.chair_user_id
         LEFT JOIN app_user su ON su.id = m.secretary_user_id
        WHERE ${meetingWhere}`,
      params
    ),
    db.query(
      `SELECT d.id,
              'deadline' AS event_type,
              d.title,
              d.deadline_type AS reference,
              d.status,
              d.due_date::date::text AS event_date,
              d.due_date AS starts_at,
              NULL::timestamptz AS ends_at,
              NULL::text AS location,
              c.committee_id,
              cm.name AS committee_name,
              c.owner_user_id AS owner_user_id,
              ou.full_name AS owner_name,
              c.owner_user_id AS primary_user_id,
              ou.full_name AS primary_user_name,
              NULL::bigint AS secondary_user_id,
              NULL::text AS secondary_user_name,
              NULL::text AS priority,
              c.case_number AS linked_case_number,
              NULL::text AS description,
              FALSE AS all_day,
              'intern'::text AS visibility,
              'deadline'::text AS source_type,
              NULL::text AS source_name,
              FALSE AS editable,
              d.created_at,
              d.updated_at,
              ('deadline-' || d.id || '@br-office.local')::text AS ical_uid
         FROM deadline d
         LEFT JOIN br_case c ON c.id = d.case_id
         LEFT JOIN committee cm ON cm.id = c.committee_id
         LEFT JOIN app_user ou ON ou.id = c.owner_user_id
        WHERE d.deleted_at IS NULL
          AND ${deadlineWhere}`,
      params
    ),
    db.query(
      `SELECT t.id,
              'task' AS event_type,
              t.title,
              COALESCE(dc.decision_number, mc.meeting_number, bc.case_number, 'Aufgabe') AS reference,
              t.status,
              t.due_date::date::text AS event_date,
              t.due_date AS starts_at,
              NULL::timestamptz AS ends_at,
              NULL::text AS location,
              COALESCE(tm.committee_id, tc.committee_id) AS committee_id,
              cm.name AS committee_name,
              t.assigned_user_id AS owner_user_id,
              au.full_name AS owner_name,
              t.assigned_user_id AS primary_user_id,
              au.full_name AS primary_user_name,
              t.backup_user_id AS secondary_user_id,
              bu.full_name AS secondary_user_name,
              t.priority,
              bc.case_number AS linked_case_number,
              t.description,
              FALSE AS all_day,
              t.confidentiality_level::text AS visibility,
              'task'::text AS source_type,
              NULL::text AS source_name,
              FALSE AS editable,
              t.created_at,
              t.updated_at,
              ('task-' || t.id || '@br-office.local')::text AS ical_uid
         FROM task t
         LEFT JOIN meeting tm ON tm.id = t.meeting_id
         LEFT JOIN br_case tc ON tc.id = t.case_id
         LEFT JOIN decision dc ON dc.id = t.decision_id
         LEFT JOIN meeting mc ON mc.id = t.meeting_id
         LEFT JOIN br_case bc ON bc.id = t.case_id
         LEFT JOIN committee cm ON cm.id = COALESCE(tm.committee_id, tc.committee_id)
         LEFT JOIN app_user au ON au.id = t.assigned_user_id
         LEFT JOIN app_user bu ON bu.id = t.backup_user_id
        WHERE t.deleted_at IS NULL
          AND ${taskWhere}`,
      params
    ),
    db.query(
      `SELECT e.id,
              'calendar' AS event_type,
              e.title,
              COALESCE(e.source_name, CASE WHEN e.source_type = 'manual' THEN 'Eigener Termin' ELSE INITCAP(e.source_type) END) AS reference,
              e.status,
              e.starts_at::date::text AS event_date,
              e.starts_at,
              e.ends_at,
              e.location,
              e.committee_id,
              c.name AS committee_name,
              e.owner_user_id,
              u.full_name AS owner_name,
              e.owner_user_id AS primary_user_id,
              u.full_name AS primary_user_name,
              NULL::bigint AS secondary_user_id,
              NULL::text AS secondary_user_name,
              e.priority,
              NULL::text AS linked_case_number,
              e.description,
              e.all_day,
              e.visibility::text AS visibility,
              e.source_type,
              e.source_name,
              TRUE AS editable,
              e.created_at,
              e.updated_at,
              COALESCE(e.source_uid, ('calendar-' || e.id || '@br-office.local'))::text AS ical_uid
         FROM calendar_event e
         LEFT JOIN committee c ON c.id = e.committee_id
         LEFT JOIN app_user u ON u.id = e.owner_user_id
        WHERE ${calendarWhere}`,
      params
    ),
  ]);

  return [...meetings.rows, ...deadlines.rows, ...tasks.rows, ...calendarItems.rows]
    .map(mapEventRow)
    .sort((a, b) => new Date(a.starts_at).getTime() - new Date(b.starts_at).getTime());
}

async function fetchTrustedProviderIcs(url, provider) {
  const parsed = new URL(url);
  if (parsed.protocol !== "https:") {
    throw Object.assign(new Error("Nur HTTPS-URLs sind erlaubt"), { status: 400, publicMessage: "Nur HTTPS-URLs sind erlaubt" });
  }

  const hostname = parsed.hostname.toLowerCase();
  const allowed = provider === "google"
    ? /(google|gmail|calendar)/i.test(hostname)
    : /(outlook|office|live|microsoft)/i.test(hostname);

  if (!allowed) {
    throw Object.assign(new Error("URL passt nicht zum Anbieter"), { status: 400, publicMessage: "URL passt nicht zum Anbieter" });
  }

  const response = await fetch(url, { method: "GET" });
  if (!response.ok) {
    throw Object.assign(new Error(`Abruf fehlgeschlagen: ${response.status}`), { status: 400, publicMessage: `Abruf fehlgeschlagen: ${response.status}` });
  }

  return response.text();
}

async function importParsedCalendar(db, parsed, options) {
  const stats = { imported: 0, updated: 0, skipped: 0, calendarName: parsed.calendarName || options.source_name || null };

  for (const rawEvent of parsed.events) {
    const event = normalizeImportedEvent(rawEvent, {
      ...options,
      source_name: options.source_name || parsed.calendarName || options.source_type?.toUpperCase(),
    });

    if (!event.title || !event.starts_at) {
      stats.skipped += 1;
      continue;
    }

    const existing = await db.query(
      `SELECT id
         FROM calendar_event
        WHERE source_type = $1
          AND source_uid = $2
          AND owner_user_id IS NOT DISTINCT FROM $3
          AND committee_id IS NOT DISTINCT FROM $4
          AND deleted_at IS NULL
        LIMIT 1`,
      [event.source_type, event.source_uid, event.owner_user_id, event.committee_id]
    );

    if (existing.rows[0]) {
      await db.query(
        `UPDATE calendar_event
            SET title = $1,
                description = $2,
                starts_at = $3,
                ends_at = $4,
                all_day = $5,
                location = $6,
                status = $7,
                priority = $8,
                visibility = $9,
                source_name = $10,
                updated_by = $11,
                updated_at = NOW()
          WHERE id = $12`,
        [
          event.title,
          event.description,
          event.starts_at,
          event.ends_at,
          event.all_day,
          event.location,
          event.status,
          event.priority,
          event.visibility,
          event.source_name,
          options.actorId,
          existing.rows[0].id,
        ]
      );
      stats.updated += 1;
    } else {
      await db.query(
        `INSERT INTO calendar_event(
           title,
           description,
           event_kind,
           source_type,
           source_uid,
           source_name,
           starts_at,
           ends_at,
           all_day,
           location,
           status,
           priority,
           visibility,
           owner_user_id,
           committee_id,
           created_by,
           updated_by
         ) VALUES (
           $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$16
         )`,
        [
          event.title,
          event.description,
          event.event_kind,
          event.source_type,
          event.source_uid,
          event.source_name,
          event.starts_at,
          event.ends_at,
          event.all_day,
          event.location,
          event.status,
          event.priority,
          event.visibility,
          event.owner_user_id,
          event.committee_id,
          options.actorId,
        ]
      );
      stats.imported += 1;
    }
  }

  return stats;
}

router.get("/meta", async (req, res, next) => {
  try {
    const data = await withDbSession(req.user, async (db) => {
      const [users, committees] = await Promise.all([
        db.query(`SELECT id, full_name, email, committee_id FROM app_user WHERE is_active = TRUE ORDER BY full_name`),
        db.query(`SELECT id, name FROM committee ORDER BY name`),
      ]);
      return {
        users: users.rows,
        committees: committees.rows,
        currentUser: { id: req.user.sub, role: req.user.role, committee_id: req.user.committee_id ?? null },
      };
    });
    res.json(data);
  } catch (err) {
    next(err);
  }
});

router.get("/events", async (req, res, next) => {
  try {
    const data = await withDbSession(req.user, (db) =>
      listEvents(db, {
        from: req.query.from,
        to: req.query.to,
        committeeId: req.query.committee_id ? Number(req.query.committee_id) : null,
        userId: req.query.user_id ? Number(req.query.user_id) : null,
      })
    );
    res.json(data);
  } catch (err) {
    next(err);
  }
});

router.post(
  "/events",
  requireMfa,
  requirePermission("calendar.write", ["admin", "chair", "secretary", "member", "advisor"]),
  validate(eventSchema),
  async (req, res, next) => {
    try {
      const payload = {
        ...req.body,
        description: normalizeNullableText(req.body.description),
        location: normalizeNullableText(req.body.location),
        source_name: normalizeNullableText(req.body.source_name),
        owner_user_id: normalizeNullableNumber(req.body.owner_user_id) ?? req.user.sub,
        committee_id: normalizeNullableNumber(req.body.committee_id),
      };

      const row = await withDbSession(req.user, async (db) => {
        await assertEventReferences(db, payload);
        const result = await db.query(
          `INSERT INTO calendar_event(
             title,
             description,
             event_kind,
             source_type,
             source_name,
             starts_at,
             ends_at,
             all_day,
             location,
             status,
             priority,
             visibility,
             owner_user_id,
             committee_id,
             created_by,
             updated_by
           ) VALUES (
             $1,$2,'personal','manual',$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$13
           ) RETURNING id`,
          [
            payload.title,
            payload.description,
            payload.source_name,
            payload.starts_at,
            payload.ends_at || null,
            Boolean(payload.all_day),
            payload.location,
            payload.status || "geplant",
            payload.priority || "mittel",
            payload.visibility || "intern",
            payload.owner_user_id,
            payload.committee_id,
            req.user.sub,
          ]
        );
        const items = await listEvents(db, { from: payload.starts_at, to: payload.ends_at || payload.starts_at });
        return items.find((item) => item.event_type === "calendar" && Number(item.id) === Number(result.rows[0].id));
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "CALENDAR_EVENT_CREATED",
        entityType: "calendar_event",
        entityId: row.id,
        ipAddress: req.ip,
        metadata: { payload },
      });

      res.status(201).json(row);
    } catch (err) {
      next(err);
    }
  }
);

router.put(
  "/events/:id",
  requireMfa,
  requirePermission("calendar.write", ["admin", "chair", "secretary", "member", "advisor"]),
  validate(updateEventSchema),
  async (req, res, next) => {
    try {
      const payload = {
        ...req.body,
        description: normalizeNullableText(req.body.description),
        location: normalizeNullableText(req.body.location),
        source_name: normalizeNullableText(req.body.source_name),
        owner_user_id: normalizeNullableNumber(req.body.owner_user_id),
        committee_id: normalizeNullableNumber(req.body.committee_id),
      };

      const row = await withDbSession(req.user, async (db) => {
        const existing = await db.query(`SELECT * FROM calendar_event WHERE id = $1 AND deleted_at IS NULL`, [req.params.id]);
        if (!existing.rows[0]) return null;

        await assertEventReferences(db, payload);

        const fields = [];
        const values = [];
        let idx = 1;
        const push = (column, value) => {
          fields.push(`${column} = $${idx}`);
          values.push(value);
          idx += 1;
        };

        [
          ["title", payload.title],
          ["description", payload.description],
          ["starts_at", payload.starts_at],
          ["ends_at", payload.ends_at],
          ["all_day", payload.all_day],
          ["location", payload.location],
          ["status", payload.status],
          ["priority", payload.priority],
          ["visibility", payload.visibility],
          ["owner_user_id", payload.owner_user_id],
          ["committee_id", payload.committee_id],
          ["source_name", payload.source_name],
        ].forEach(([column, value]) => {
          if (value !== undefined) push(column, value);
        });

        if (!fields.length) {
          throw Object.assign(new Error("Keine Felder zum Aktualisieren"), { status: 400, publicMessage: "Keine Felder zum Aktualisieren" });
        }

        push("updated_by", req.user.sub);
        fields.push(`updated_at = NOW()`);
        values.push(req.params.id);

        await db.query(`UPDATE calendar_event SET ${fields.join(", ")} WHERE id = $${idx}`, values);
        const items = await listEvents(db, {
          from: payload.starts_at || existing.rows[0].starts_at,
          to: payload.ends_at || existing.rows[0].ends_at || payload.starts_at || existing.rows[0].starts_at,
        });
        return items.find((item) => item.event_type === "calendar" && Number(item.id) === Number(req.params.id));
      });

      if (!row) return res.status(404).json({ error: "Nicht gefunden" });

      await writeAudit({
        actorId: req.user.sub,
        action: "CALENDAR_EVENT_UPDATED",
        entityType: "calendar_event",
        entityId: row.id,
        ipAddress: req.ip,
        metadata: { changes: payload },
      });

      res.json(row);
    } catch (err) {
      next(err);
    }
  }
);

router.delete(
  "/events/:id",
  requireMfa,
  requirePermission("calendar.write", ["admin", "chair", "secretary", "member", "advisor"]),
  async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const existing = await db.query(`SELECT id, title FROM calendar_event WHERE id = $1 AND deleted_at IS NULL`, [req.params.id]);
        if (!existing.rows[0]) return null;
        await db.query(`UPDATE calendar_event SET deleted_at = NOW(), updated_by = $2, updated_at = NOW() WHERE id = $1`, [req.params.id, req.user.sub]);
        return existing.rows[0];
      });

      if (!row) return res.status(404).json({ error: "Nicht gefunden" });

      await writeAudit({
        actorId: req.user.sub,
        action: "CALENDAR_EVENT_DELETED",
        entityType: "calendar_event",
        entityId: row.id,
        ipAddress: req.ip,
      });

      res.json({ ok: true, row });
    } catch (err) {
      next(err);
    }
  }
);

router.get("/export.ics", async (req, res, next) => {
  try {
    const events = await withDbSession(req.user, (db) =>
      listEvents(db, {
        from: req.query.from,
        to: req.query.to,
        committeeId: req.query.committee_id ? Number(req.query.committee_id) : null,
        userId: req.query.user_id ? Number(req.query.user_id) : null,
      })
    );

    const ics = buildIcsCalendar(events, { name: "BR-Office Kalender" });
    res.setHeader("Content-Type", "text/calendar; charset=utf-8");
    res.setHeader("Content-Disposition", 'attachment; filename="br-office-calendar.ics"');
    res.send(ics);
  } catch (err) {
    next(err);
  }
});

router.post(
  "/import/ics",
  requireMfa,
  requirePermission("calendar.write", ["admin", "chair", "secretary", "member", "advisor"]),
  upload.single("file"),
  async (req, res, next) => {
    try {
      const rawText = req.file?.buffer?.toString("utf8") || req.body?.ics || "";
      if (!rawText.trim()) {
        return res.status(400).json({ error: "ICS-Datei oder ICS-Text fehlt" });
      }

      const ownerUserId = normalizeNullableNumber(req.body.owner_user_id) ?? req.user.sub;
      const committeeId = normalizeNullableNumber(req.body.committee_id);
      const sourceName = normalizeNullableText(req.body.source_name);
      const visibility = req.body.visibility || "intern";

      const result = await withDbSession(req.user, async (db) => {
        await assertEventReferences(db, { owner_user_id: ownerUserId, committee_id: committeeId });
        const parsed = parseIcsCalendar(rawText);
        return importParsedCalendar(db, parsed, {
          actorId: req.user.sub,
          owner_user_id: ownerUserId,
          committee_id: committeeId,
          source_name: sourceName,
          visibility,
          event_kind: "imported",
          source_type: "ical",
        });
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "CALENDAR_ICS_IMPORTED",
        entityType: "calendar_event",
        entityId: null,
        ipAddress: req.ip,
        metadata: result,
      });

      res.status(201).json(result);
    } catch (err) {
      next(err);
    }
  }
);

for (const provider of ["outlook", "google"]) {
  router.post(
    `/sync/${provider}`,
    requireMfa,
    requirePermission("calendar.write", ["admin", "chair", "secretary", "member", "advisor"]),
    async (req, res, next) => {
      try {
        if (!req.body?.url) return res.status(400).json({ error: "URL fehlt" });

        const ownerUserId = normalizeNullableNumber(req.body.owner_user_id) ?? req.user.sub;
        const committeeId = normalizeNullableNumber(req.body.committee_id);
        const sourceName = normalizeNullableText(req.body.source_name) || provider;
        const visibility = req.body.visibility || "intern";

        const rawText = await fetchTrustedProviderIcs(req.body.url, provider);

        const result = await withDbSession(req.user, async (db) => {
          await assertEventReferences(db, { owner_user_id: ownerUserId, committee_id: committeeId });
          const parsed = parseIcsCalendar(rawText);
          return importParsedCalendar(db, parsed, {
            actorId: req.user.sub,
            owner_user_id: ownerUserId,
            committee_id: committeeId,
            source_name: sourceName,
            visibility,
            event_kind: "synced",
            source_type: provider,
          });
        });

        await writeAudit({
          actorId: req.user.sub,
          action: `CALENDAR_${provider.toUpperCase()}_SYNC`,
          entityType: "calendar_event",
          entityId: null,
          ipAddress: req.ip,
          metadata: { ...result, sourceName, url: req.body.url },
        });

        res.status(201).json(result);
      } catch (err) {
        next(err);
      }
    }
  );
}

export default router;
