"use server";

import crypto from "crypto";
import { revalidatePath } from "next/cache";
import { hasPermission } from "@/lib/adminAuth";
import { getSessionClientId } from "@/lib/auth";
import { connectDB } from "@/lib/db";
import { Calendar } from "@/models/Calendar";
import { CalendarEvent } from "@/models/CalendarEvent";
import { canEditCalendar } from "@/lib/calendars";
import { pushEventToGoogle, deleteEventFromGoogle } from "@/lib/googleCalendar";
import { runGoogleBackfillForCalendar } from "@/lib/googleSync";

const CAL_PATH = "/de/admin/calendar";

type FormState = { error?: string };

function genToken() {
  return crypto.randomBytes(24).toString("hex");
}

// Lazy angelegt statt per Migration/Seed — bewusst upsert-basiert (statt
// find-dann-create), damit zwei gleichzeitige erste Seitenaufrufe nicht
// versehentlich zwei "orders"-Systemkalender anlegen.
export async function getOrCreateOrdersCalendar() {
  await connectDB();
  const doc = await Calendar.findOneAndUpdate(
    { kind: "orders" },
    {
      $setOnInsert: {
        name: "Aufträge",
        color: "#38bdf8",
        kind: "orders",
        ownerId: null,
        visibility: "team",
        sharedWith: [],
        icsToken: genToken(),
        googleCalendarId: "primary",
      },
    },
    // setDefaultsOnInsert sorgt zusätzlich dafür, dass künftige neue
    // Schema-Felder mit einem default auch bei diesem Upsert-Pfad greifen —
    // $setOnInsert allein prüft das nicht automatisch nach.
    { upsert: true, returnDocument: "after", setDefaultsOnInsert: true }
  ).lean();
  return doc;
}

// Systemansicht "Conventions" — spiegelt Conventions mit "Team vor Ort"
// (assignedStaffIds) in den Kalender, ohne eigene CalendarEvents zu schreiben
// (die Seite liest direkt aus dem Convention-Modell, genau wie bei "orders").
export async function getOrCreateConventionsCalendar() {
  await connectDB();
  const doc = await Calendar.findOneAndUpdate(
    { kind: "conventions" },
    {
      $setOnInsert: {
        name: "Conventions",
        color: "#f59e0b",
        kind: "conventions",
        ownerId: null,
        visibility: "team",
        sharedWith: [],
        icsToken: genToken(),
        googleCalendarId: "primary",
      },
    },
    { upsert: true, returnDocument: "after", setDefaultsOnInsert: true }
  ).lean();
  return doc;
}

// Analog zu getOrCreateOrdersCalendar, für die Systemansicht "Abwesenheiten"
// (aus dem Absence-Modell) — ebenfalls upsert-basiert gegen doppelte Anlage.
export async function getOrCreateAbsencesCalendar() {
  await connectDB();
  const doc = await Calendar.findOneAndUpdate(
    { kind: "absences" },
    {
      $setOnInsert: {
        name: "Abwesenheiten",
        color: "#f472b6",
        kind: "absences",
        ownerId: null,
        visibility: "team",
        sharedWith: [],
        icsToken: genToken(),
        googleCalendarId: "primary",
      },
    },
    { upsert: true, returnDocument: "after", setDefaultsOnInsert: true }
  ).lean();
  return doc;
}

// Persönlicher "Meine Termine"-Kalender pro Mitarbeiter — lazy beim ersten
// Kalender-Seitenaufruf angelegt, damit man immer irgendwo manuell einen
// Termin (privates Shooting, sonstiger Termin) eintragen kann, ohne erst
// einen Kalender anlegen zu müssen. Upsert gegen doppelte Anlage.
export async function getOrCreatePersonalCalendar(clientId: string) {
  await connectDB();
  const doc = await Calendar.findOneAndUpdate(
    { ownerId: clientId, isPersonalDefault: true },
    {
      $setOnInsert: {
        name: "Meine Termine",
        color: "#a78bfa",
        kind: "custom",
        ownerId: clientId,
        isPersonalDefault: true,
        visibility: "private",
        sharedWith: [],
        icsToken: genToken(),
        googleCalendarId: "primary",
      },
    },
    { upsert: true, returnDocument: "after", setDefaultsOnInsert: true }
  ).lean();
  return doc;
}

export async function createCalendarAction(_prev: FormState, formData: FormData): Promise<FormState> {
  if (!(await hasPermission("calendars_create"))) return { error: "Keine Berechtigung." };
  const clientId = await getSessionClientId();
  if (!clientId) return { error: "Nicht eingeloggt." };

  const name = String(formData.get("name") || "").trim();
  if (!name) return { error: "Bitte einen Namen eingeben." };
  const color = String(formData.get("color") || "#38bdf8").trim() || "#38bdf8";
  const visibility = formData.get("visibility") === "team" ? "team" : "private";

  await connectDB();
  await Calendar.create({
    name,
    color,
    kind: "custom",
    ownerId: clientId,
    visibility,
    sharedWith: [],
    icsToken: genToken(),
  });

  revalidatePath(CAL_PATH);
  return {};
}

export async function updateCalendarAction(calendarId: string, formData: FormData) {
  if (!(await hasPermission("calendars_edit"))) return;
  const clientId = await getSessionClientId();
  if (!clientId) return;

  await connectDB();
  const cal = await Calendar.findById(calendarId);
  if (!cal || cal.kind !== "custom" || String(cal.ownerId) !== clientId) return;

  const name = String(formData.get("name") || "").trim();
  if (name) cal.name = name;
  cal.color = String(formData.get("color") || cal.color).trim() || cal.color;
  cal.visibility = formData.get("visibility") === "team" ? "team" : "private";
  cal.publicVisible = formData.get("publicVisible") === "on";
  await cal.save();

  revalidatePath(CAL_PATH);
}

export async function deleteCalendarAction(calendarId: string) {
  if (!(await hasPermission("calendars_delete"))) return;
  const clientId = await getSessionClientId();
  if (!clientId) return;

  await connectDB();
  const cal = await Calendar.findById(calendarId);
  if (!cal || cal.kind !== "custom" || String(cal.ownerId) !== clientId) return;

  await CalendarEvent.deleteMany({ calendarId });
  await cal.deleteOne();

  revalidatePath(CAL_PATH);
}

export async function addCalendarShareAction(calendarId: string, staffId: string) {
  if (!(await hasPermission("calendars_edit"))) return;
  const clientId = await getSessionClientId();
  if (!clientId || !staffId) return;

  await connectDB();
  const cal = await Calendar.findById(calendarId).lean();
  if (!cal || cal.kind !== "custom" || String(cal.ownerId) !== clientId) return;
  await Calendar.findByIdAndUpdate(calendarId, { $addToSet: { sharedWith: staffId } });

  revalidatePath(CAL_PATH);
}

export async function removeCalendarShareAction(calendarId: string, staffId: string) {
  if (!(await hasPermission("calendars_edit"))) return;
  const clientId = await getSessionClientId();
  if (!clientId) return;

  await connectDB();
  const cal = await Calendar.findById(calendarId).lean();
  if (!cal || cal.kind !== "custom" || String(cal.ownerId) !== clientId) return;
  await Calendar.findByIdAndUpdate(calendarId, { $pull: { sharedWith: staffId } });

  revalidatePath(CAL_PATH);
}

// Für den Systemkalender "orders" (kein Besitzer) reicht die orders-Berechtigung,
// für eigene Kalender muss man Besitzer sein.
export async function regenerateIcsTokenAction(calendarId: string) {
  const clientId = await getSessionClientId();
  if (!clientId) return;

  await connectDB();
  const cal = await Calendar.findById(calendarId);
  if (!cal) return;

  if (cal.kind === "orders") {
    if (!(await hasPermission("orders_edit"))) return;
  } else {
    if (!(await hasPermission("calendars_edit"))) return;
    if (String(cal.ownerId) !== clientId) return;
  }

  cal.icsToken = genToken();
  await cal.save();

  revalidatePath(CAL_PATH);
}

export async function createCalendarEventAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const clientId = await getSessionClientId();
  if (!clientId) return { error: "Nicht eingeloggt." };
  const calendarId = String(formData.get("calendarId") || "");
  const title = String(formData.get("title") || "").trim();
  const startRaw = String(formData.get("start") || "");
  if (!title) return { error: "Bitte einen Titel eingeben." };
  if (!startRaw) return { error: "Bitte einen Start-Zeitpunkt angeben." };

  await connectDB();
  const cal = await Calendar.findById(calendarId).lean();
  if (!cal) return { error: "Kalender nicht gefunden." };
  const hasCalendars = await hasPermission("calendars_edit");
  if (!canEditCalendar(cal, clientId, hasCalendars)) {
    return { error: "Keine Berechtigung für diesen Kalender." };
  }

  const start = new Date(startRaw);
  if (Number.isNaN(start.getTime())) return { error: "Ungültiges Startdatum." };
  const endRaw = String(formData.get("end") || "");
  const end = endRaw ? new Date(endRaw) : null;
  if (end && Number.isNaN(end.getTime())) return { error: "Ungültiges Enddatum." };

  const description = String(formData.get("description") || "").trim();
  const allDay = formData.get("allDay") === "on";
  const created = await CalendarEvent.create({
    calendarId,
    title,
    description,
    start,
    end,
    allDay,
    createdBy: clientId,
  });

  const googleEventId = await pushEventToGoogle(calendarId, "", { title, description, start, end, allDay });
  if (googleEventId) await CalendarEvent.findByIdAndUpdate(created._id, { googleEventId });

  revalidatePath(CAL_PATH);
  return {};
}

export async function deleteCalendarEventAction(eventId: string) {
  const clientId = await getSessionClientId();
  if (!clientId) return;

  await connectDB();
  const event = await CalendarEvent.findById(eventId);
  if (!event) return;
  const cal = await Calendar.findById(event.calendarId).lean();
  if (!cal) return;
  const hasCalendars = await hasPermission("calendars_edit");
  if (!canEditCalendar(cal, clientId, hasCalendars)) return;

  if (event.googleEventId) await deleteEventFromGoogle(String(event.calendarId), event.googleEventId);
  await event.deleteOne();
  revalidatePath(CAL_PATH);
}

// Überträgt alle bereits vorhandenen Termine, die noch keine googleEventId
// haben, einmalig zu Google — sonst blieben Termine, die vor dem Verbinden
// angelegt wurden, für immer unsichtbar in Google. Wird direkt nach
// erfolgreichem OAuth-Callback aufgerufen, kann aber auch beliebig oft
// erneut aufgerufen werden (idempotent, da nur googleEventId === "" greift).
export async function backfillGoogleSync(calendarId: string): Promise<void> {
  const clientId = await getSessionClientId();
  if (!clientId) return;

  await connectDB();
  const cal = await Calendar.findById(calendarId).lean();
  if (!cal) return;

  const authorized =
    cal.kind === "orders" ? await hasPermission("orders_edit") : (await hasPermission("calendars_edit")) && String(cal.ownerId) === clientId;
  if (!authorized) return;

  await runGoogleBackfillForCalendar(calendarId);
}

// Für den Systemkalender "orders" reicht die orders-Berechtigung, für
// eigene Kalender muss man Besitzer sein — analog zu regenerateIcsTokenAction.
export async function disconnectGoogleAction(calendarId: string) {
  const clientId = await getSessionClientId();
  if (!clientId) return;

  await connectDB();
  const cal = await Calendar.findById(calendarId).lean();
  if (!cal) return;

  const authorized =
    cal.kind === "orders" ? await hasPermission("orders_edit") : (await hasPermission("calendars_edit")) && String(cal.ownerId) === clientId;
  if (!authorized) return;

  await Calendar.findByIdAndUpdate(calendarId, {
    googleConnectedBy: null,
    googleAccessToken: "",
    googleRefreshToken: "",
    googleTokenExpiry: null,
  });

  revalidatePath(CAL_PATH);
}
