import { redirect } from "next/navigation";
import Link from "next/link";
import { isAdminSession, hasPermission, getAllPermissions } from "@/lib/adminAuth";
import { getSessionClientId } from "@/lib/auth";
import { connectDB } from "@/lib/db";
import { Order } from "@/models/Order";
import { Client } from "@/models/Client";
import { Calendar } from "@/models/Calendar";
import { CalendarEvent } from "@/models/CalendarEvent";
import { Absence } from "@/models/Absence";
import { Convention } from "@/models/Convention";
import {
  getOrCreateOrdersCalendar,
  getOrCreateAbsencesCalendar,
  getOrCreateConventionsCalendar,
  getOrCreatePersonalCalendar,
  deleteCalendarEventAction,
} from "@/lib/actions/calendars";
import { canViewCalendar, canEditCalendar } from "@/lib/calendars";
import { getSiteUrl } from "@/lib/seo";
import { ABSENCE_TYPE_LABELS, type AbsenceType } from "@/lib/absenceConstants";
import { AdminNavLinks } from "@/components/admin/AdminNavLinks";
import { AdminPageHeader } from "@/components/admin/AdminPageHeader";
import { CalendarSidebar, type SidebarCalendar } from "@/components/admin/CalendarSidebar";
import { NewCalendarEventForm } from "@/components/admin/NewCalendarEventForm";
import { DeleteEventButton } from "@/components/admin/DeleteEventButton";
import { CalendarDay, type DayItem } from "@/components/admin/CalendarDay";
import { ORDER_TAG_COLORS, ORDER_TAG_LABELS, effectiveEventAddress, type OrderTag } from "@/lib/orderConstants";

export const dynamic = "force-dynamic";

const WEEKDAY_LABELS = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
const MONTH_LABELS = [
  "Januar", "Februar", "März", "April", "Mai", "Juni",
  "Juli", "August", "September", "Oktober", "November", "Dezember",
];

function dayKey(d: Date) {
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}

function parseMonth(param: string | undefined): { year: number; monthIndex: number } {
  const now = new Date();
  if (param && /^\d{4}-\d{2}$/.test(param)) {
    const [y, m] = param.split("-").map(Number);
    if (m >= 1 && m <= 12) return { year: y, monthIndex: m - 1 };
  }
  return { year: now.getFullYear(), monthIndex: now.getMonth() };
}

function monthParam(year: number, monthIndex: number) {
  const y = monthIndex < 0 ? year - 1 : monthIndex > 11 ? year + 1 : year;
  const m = ((monthIndex % 12) + 12) % 12;
  return `${y}-${String(m + 1).padStart(2, "0")}`;
}

type GridItem = {
  id: string;
  start: Date;
  end?: Date | null;
  title: string;
  color: string;
  href: string | null;
  tooltip: string;
  deletable: boolean;
};

function timeLabel(item: GridItem): string {
  const start = item.start.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
  if (!item.end) return start;
  const end = item.end.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
  return `${start}–${end}`;
}

// Bei einem mehrtägigen Termin (z. B. eine Woche Urlaub) wäre eine Uhrzeit
// in jeder einzelnen Tageszelle nur verwirrend — dort reicht der Titel.
function isMultiDay(item: GridItem): boolean {
  return Boolean(item.end) && dayKey(item.start) !== dayKey(item.end as Date);
}

function cellLabel(item: GridItem): string {
  return isMultiDay(item) ? item.title : `${timeLabel(item)} ${item.title}`;
}

const GOOGLE_ERROR_LABELS: Record<string, string> = {
  state: "Sicherheitsprüfung fehlgeschlagen, bitte erneut versuchen.",
  auth: "Bitte erneut einloggen und noch einmal versuchen.",
  notfound: "Kalender nicht gefunden.",
  forbidden: "Keine Berechtigung für diesen Kalender.",
  token: "Google hat keine gültigen Zugangsdaten zurückgegeben, bitte erneut versuchen.",
};

export default async function AdminCalendarPage({
  searchParams,
}: {
  searchParams: Promise<{ month?: string; cals?: string; vacation?: string; googleConnected?: string; googleError?: string }>;
}) {
  if (!(await isAdminSession())) redirect("/de/login?next=%2Fde%2Fadmin%2Fcalendar");
  const hasOrders = await hasPermission("orders_view");
  const hasOrdersEdit = await hasPermission("orders_edit");
  const hasCalendars = await hasPermission("calendars_view");
  const hasCalendarsEdit = await hasPermission("calendars_edit");
  const hasCalendarsCreate = await hasPermission("calendars_create");
  if (!hasOrders && !hasCalendars) redirect("/de/account");

  const permissions = await getAllPermissions();
  const clientId = (await getSessionClientId())!;
  const { month, cals, vacation, googleConnected, googleError } = await searchParams;
  const { year, monthIndex } = parseMonth(month);

  const monthStart = new Date(year, monthIndex, 1);
  const firstWeekday = (monthStart.getDay() + 6) % 7;
  const gridStart = new Date(year, monthIndex, 1 - firstWeekday);
  const lastDayOfMonth = new Date(year, monthIndex + 1, 0);
  const lastWeekday = (lastDayOfMonth.getDay() + 6) % 7;
  const gridEnd = new Date(year, monthIndex + 1, 0 + (6 - lastWeekday) + 1);

  const days: Date[] = [];
  for (let d = new Date(gridStart); d < gridEnd; d.setDate(d.getDate() + 1)) days.push(new Date(d));

  await connectDB();
  const now = new Date();

  // Persönlichen "Meine Termine"-Kalender sicherstellen, damit unten immer ein
  // Ziel für manuelle Termine da ist (auch wenn noch kein Kalender angelegt wurde).
  if (hasCalendars) await getOrCreatePersonalCalendar(clientId);

  const [ordersCalendar, absencesCalendar, conventionsCalendar, allCustomCalendars, allStaffDocs] = await Promise.all([
    hasOrders ? getOrCreateOrdersCalendar() : null,
    getOrCreateAbsencesCalendar(),
    getOrCreateConventionsCalendar(),
    Calendar.find({ kind: "custom" }).lean(),
    Client.find({ role: "staff" }).select("name").lean(),
  ]);

  const viewableCustom = allCustomCalendars.filter((c) => canViewCalendar(c, clientId, hasOrders, hasCalendars));
  const clientById = new Map(allStaffDocs.map((c) => [String(c._id), c]));
  const allStaff = allStaffDocs.map((c) => ({ id: String(c._id), name: c.name }));

  const siteUrl = getSiteUrl();
  const sidebarCalendars: SidebarCalendar[] = [];
  if (ordersCalendar) {
    sidebarCalendars.push({
      id: String(ordersCalendar._id),
      name: ordersCalendar.name,
      color: ordersCalendar.color,
      kind: "orders",
      visibility: "team",
      canManage: hasOrdersEdit,
      icsUrl: `${siteUrl}/api/calendar/${ordersCalendar._id}/feed.ics?token=${ordersCalendar.icsToken}`,
      sharedWith: [],
      googleConnected: Boolean(ordersCalendar.googleRefreshToken),
      publicVisible: false,
    });
  }
  if (conventionsCalendar) {
    sidebarCalendars.push({
      id: String(conventionsCalendar._id),
      name: conventionsCalendar.name,
      color: conventionsCalendar.color,
      kind: "conventions",
      visibility: "team",
      // Einträge kommen automatisch aus Conventions mit "Team vor Ort" —
      // nicht manuell editierbar (siehe canEditCalendar).
      canManage: false,
      icsUrl: `${siteUrl}/api/calendar/${conventionsCalendar._id}/feed.ics?token=${conventionsCalendar.icsToken}`,
      sharedWith: [],
      googleConnected: false,
      publicVisible: false,
    });
  }
  if (absencesCalendar) {
    sidebarCalendars.push({
      id: String(absencesCalendar._id),
      name: absencesCalendar.name,
      color: absencesCalendar.color,
      kind: "absences",
      visibility: "team",
      // Keine Bearbeitung/Google-Verbindung über diesen Kalender möglich —
      // Einträge kommen nur über /admin/absences zustande (siehe canEditCalendar).
      canManage: false,
      icsUrl: `${siteUrl}/api/calendar/${absencesCalendar._id}/feed.ics?token=${absencesCalendar.icsToken}`,
      sharedWith: [],
      googleConnected: false,
      publicVisible: false,
    });
  }
  for (const c of viewableCustom) {
    sidebarCalendars.push({
      id: String(c._id),
      name: c.name,
      color: c.color,
      kind: "custom",
      visibility: c.visibility,
      canManage: String(c.ownerId) === clientId,
      icsUrl: `${siteUrl}/api/calendar/${c._id}/feed.ics?token=${c.icsToken}`,
      sharedWith: c.sharedWith.map((id) => ({ id: String(id), name: clientById.get(String(id))?.name ?? "Unbekannt" })),
      googleConnected: Boolean(c.googleRefreshToken),
      publicVisible: c.publicVisible,
    });
  }

  const defaultVisible = sidebarCalendars.map((c) => c.id);
  // Direkter Sprung von der Admin-Navigation ("Urlaub") — blendet alles
  // andere aus und zeigt nur den öffentlich sichtbaren Kalender, statt ihn
  // erst in der allgemeinen Kalenderliste suchen zu müssen.
  const vacationCalendarId = viewableCustom.find((c) => c.publicVisible)?._id;
  const isVacationView = vacation === "1" && Boolean(vacationCalendarId);
  const visibleIds = isVacationView ? [String(vacationCalendarId)] : cals ? cals.split(",").filter(Boolean) : defaultVisible;
  const visibleSet = new Set(visibleIds);

  const visibleCustomIds = viewableCustom.filter((c) => visibleSet.has(String(c._id))).map((c) => c._id);
  const editableCalendarIds = new Set(
    viewableCustom.filter((c) => canEditCalendar(c, clientId, hasCalendarsEdit)).map((c) => String(c._id))
  );
  const editableCalendars = sidebarCalendars.filter((c) => c.kind === "custom" && editableCalendarIds.has(c.id));

  const ordersVisible = hasOrders && visibleSet.has(String(ordersCalendar?._id));
  const absencesVisible = visibleSet.has(String(absencesCalendar?._id));
  const conventionsVisible = visibleSet.has(String(conventionsCalendar?._id));

  // Conventions mit "Team vor Ort" (assignedStaffIds) — Systemansicht, kein
  // eigenes CalendarEvent, direkt aus dem Convention-Modell gelesen.
  const [monthConventions, upcomingConventions] = await Promise.all([
    conventionsVisible
      ? Convention.find({
          "assignedStaffIds.0": { $exists: true },
          startDate: { $lt: gridEnd },
          endDate: { $gte: gridStart },
        }).lean()
      : Convention.find({ _id: null }).lean(),
    conventionsVisible
      ? Convention.find({ "assignedStaffIds.0": { $exists: true }, endDate: { $gte: now } })
          .sort({ startDate: 1 })
          .limit(8)
          .lean()
      : Convention.find({ _id: null }).lean(),
  ]);
  const [monthOrders, monthEvents, monthAbsences, upcomingOrders, upcomingEvents, upcomingAbsences] = await Promise.all([
    ordersVisible
      ? Order.find({ shootDate: { $gte: gridStart, $lt: gridEnd }, rejected: false }).lean()
      : Order.find({ _id: null }).lean(),
    visibleCustomIds.length
      ? CalendarEvent.find({
          calendarId: { $in: visibleCustomIds },
          start: { $lt: gridEnd },
          $or: [{ end: { $gte: gridStart } }, { end: null, start: { $gte: gridStart } }],
        }).lean()
      : CalendarEvent.find({ _id: null }).lean(),
    absencesVisible
      ? Absence.find({ startDate: { $lt: gridEnd }, endDate: { $gte: gridStart } }).lean()
      : Absence.find({ _id: null }).lean(),
    ordersVisible
      ? Order.find({ shootDate: { $gte: now }, rejected: false }).sort({ shootDate: 1 }).limit(8).lean()
      : Order.find({ _id: null }).lean(),
    visibleCustomIds.length
      ? CalendarEvent.find({ calendarId: { $in: visibleCustomIds }, start: { $gte: now } }).sort({ start: 1 }).limit(8).lean()
      : CalendarEvent.find({ _id: null }).lean(),
    absencesVisible
      ? Absence.find({ endDate: { $gte: now } }).sort({ startDate: 1 }).limit(8).lean()
      : Absence.find({ _id: null }).lean(),
  ]);

  const orderClientIds = [...new Set(monthOrders.concat(upcomingOrders).map((o) => String(o.clientId)))];
  const orderClients = orderClientIds.length ? await Client.find({ _id: { $in: orderClientIds } }).lean() : [];
  const orderClientById = new Map(orderClients.map((c) => [String(c._id), c]));

  const calendarById = new Map(sidebarCalendars.map((c) => [c.id, c]));

  const ordersToGridItems = (list: typeof monthOrders): GridItem[] =>
    list.map((o) => ({
      id: `order-${o._id}`,
      start: new Date(o.shootDate as Date),
      end: o.shootEndDate ? new Date(o.shootEndDate) : null,
      title: `${orderClientById.get(String(o.clientId))?.name ?? o.characterOrConcept}`,
      color: ORDER_TAG_COLORS[o.tag as OrderTag],
      href: `/de/admin/orders/${o._id}`,
      tooltip: `${o.characterOrConcept} · ${orderClientById.get(String(o.clientId))?.name ?? ""} · ${ORDER_TAG_LABELS[o.tag as OrderTag]}${effectiveEventAddress(o) ? ` · 📍 ${effectiveEventAddress(o)}` : ""}`,
      // Auftragstermine laufen nie über diesen Weg löschbar — das passiert
      // über die jeweilige Auftragsseite (siehe canEditCalendar-Kommentar).
      deletable: false,
    }));

  const absencesToGridItems = (list: typeof monthAbsences): GridItem[] =>
    list.map((a) => ({
      id: `absence-${a._id}`,
      start: new Date(a.startDate),
      end: new Date(a.endDate),
      title: `${clientById.get(String(a.clientId))?.name ?? "Team"} – ${ABSENCE_TYPE_LABELS[a.type as AbsenceType]}`,
      color: absencesCalendar?.color ?? "#f472b6",
      href: "/de/admin/absences",
      tooltip: `${clientById.get(String(a.clientId))?.name ?? ""} · ${ABSENCE_TYPE_LABELS[a.type as AbsenceType]}${a.note ? " · " + a.note : ""}`,
      // Bearbeitung/Löschung nur über die Abwesenheits-Seite selbst (siehe
      // canEditCalendar) — hier nur Anzeige.
      deletable: false,
    }));

  const eventsToGridItems = (list: typeof monthEvents): GridItem[] =>
    list.map((e) => ({
      id: `event-${e._id}`,
      start: new Date(e.start),
      end: e.end ? new Date(e.end) : null,
      title: e.title,
      color: calendarById.get(String(e.calendarId))?.color ?? "#a78bfa",
      href: null,
      tooltip: `${e.title}${e.description ? " · " + e.description : ""} · ${calendarById.get(String(e.calendarId))?.name ?? ""}`,
      // Bisher war ein Termin nur über die "Nächste Termine"-Liste löschbar
      // (auf 8 anstehende Einträge begrenzt) — vergangene/laufende mehrtägige
      // Termine wie Urlaub tauchten dort nie auf und waren dadurch gar nicht
      // löschbar. Jetzt zusätzlich direkt im Monatsraster möglich.
      deletable: editableCalendarIds.has(String(e.calendarId)),
    }));

  const conventionsToGridItems = (list: typeof monthConventions): GridItem[] =>
    list.map((c) => {
      const names = (c.assignedStaffIds || [])
        .map((id) => clientById.get(String(id))?.name)
        .filter(Boolean)
        .join(", ");
      return {
        id: `conv-${c._id}`,
        start: new Date(c.startDate),
        end: new Date(c.endDate),
        title: c.name,
        color: conventionsCalendar?.color ?? "#f59e0b",
        href: "/de/admin/conventions",
        tooltip: `${c.name}${c.location ? ` · 📍 ${c.location}` : ""}${names ? ` · Team: ${names}` : ""}`,
        deletable: false,
      };
    });

  const gridItems = [
    ...ordersToGridItems(monthOrders),
    ...eventsToGridItems(monthEvents),
    ...absencesToGridItems(monthAbsences),
    ...conventionsToGridItems(monthConventions),
  ];
  const itemsByDay = new Map<string, GridItem[]>();
  // Mehrtägige Termine (z. B. eine Woche Urlaub) sollen an jedem betroffenen
  // Tag im Monatsraster auftauchen, nicht nur am Starttag — sonst wirkt es
  // im Kalender so, als wäre die Person nur an einem einzigen Tag weg.
  for (const item of gridItems) {
    const lastDay = item.end && item.end.getTime() > item.start.getTime() ? item.end : item.start;
    for (let d = new Date(item.start); dayKey(d) <= dayKey(lastDay); d.setDate(d.getDate() + 1)) {
      const key = dayKey(d);
      if (!itemsByDay.has(key)) itemsByDay.set(key, []);
      itemsByDay.get(key)!.push(item);
    }
  }
  for (const list of itemsByDay.values()) list.sort((a, b) => a.start.getTime() - b.start.getTime());

  const upcoming = [
    ...ordersToGridItems(upcomingOrders),
    ...eventsToGridItems(upcomingEvents),
    ...absencesToGridItems(upcomingAbsences),
    ...conventionsToGridItems(upcomingConventions),
  ]
    .sort((a, b) => a.start.getTime() - b.start.getTime())
    .slice(0, 8);

  const todayKey = dayKey(now);
  const prevHref = `/de/admin/calendar?month=${monthParam(year, monthIndex - 1)}${cals ? `&cals=${cals}` : ""}`;
  const nextHref = `/de/admin/calendar?month=${monthParam(year, monthIndex + 1)}${cals ? `&cals=${cals}` : ""}`;
  const todayHref = `/de/admin/calendar${cals ? `?cals=${cals}` : ""}`;

  return (
    <main className="px-6 pt-4 pb-24 max-w-6xl mx-auto">
      <AdminNavLinks permissions={permissions} />
      <AdminPageHeader
        title={isVacationView ? "Urlaub" : "Kalender"}
        subtitle={
          isVacationView
            ? "Nur der Urlaubskalender, alle anderen sind ausgeblendet — praktisch, um schnell zu sehen, wer wann weg ist."
            : "Mehrere Kalender parallel — jeder mit eigenem Abo-Link für Google/Apple/Outlook, der sich automatisch aktualisiert."
        }
      />
      {isVacationView && (
        <Link
          href="/de/admin/calendar"
          className="inline-block mb-6 text-xs text-[var(--rk-ink-muted)] hover:text-[var(--rk-ink)] underline transition-colors"
        >
          ← Alle Kalender anzeigen
        </Link>
      )}

      {googleConnected && (
        <p className="mb-6 text-sm text-emerald-400 border border-emerald-400/30 rounded-lg px-4 py-2.5">
          Mit Google verbunden — neue Termine werden ab jetzt automatisch übertragen.
        </p>
      )}
      {googleError && (
        <p className="mb-6 text-sm text-red-400 border border-red-400/30 rounded-lg px-4 py-2.5">
          Google-Verbindung fehlgeschlagen: {GOOGLE_ERROR_LABELS[googleError] ?? googleError}
        </p>
      )}

      <div className="grid grid-cols-1 lg:grid-cols-[16rem_1fr] gap-6 items-start">
        <div className="lg:sticky lg:top-32">
          <CalendarSidebar calendars={sidebarCalendars} visibleIds={visibleIds} allStaff={allStaff} canCreate={hasCalendarsCreate} />
        </div>

        <div>
          <div className="flex items-center justify-between mb-6">
            <Link href={prevHref} className="text-xs border border-white/15 rounded-full px-3.5 py-1.5 hover:border-white/50 transition-colors">
              ← Vorheriger Monat
            </Link>
            <div className="flex items-center gap-3">
              <h2 className="text-sm font-medium text-[var(--rk-ink)]">
                {MONTH_LABELS[monthIndex]} {year}
              </h2>
              <Link href={todayHref} className="text-xs text-[var(--rk-ink-muted)] hover:text-[var(--rk-ink)] underline transition-colors">
                Heute
              </Link>
            </div>
            <Link href={nextHref} className="text-xs border border-white/15 rounded-full px-3.5 py-1.5 hover:border-white/50 transition-colors">
              Nächster Monat →
            </Link>
          </div>

          <div className="grid grid-cols-7 gap-1.5 mb-10">
            {WEEKDAY_LABELS.map((w) => (
              <div key={w} className="text-center text-[0.65rem] uppercase tracking-[0.1em] text-[var(--rk-ink-muted)] pb-1">
                {w}
              </div>
            ))}
            {days.map((d) => {
              const key = dayKey(d);
              const dayItems: DayItem[] = (itemsByDay.get(key) ?? []).map((item) => ({
                id: item.id,
                label: cellLabel(item),
                color: item.color,
                href: item.href,
                tooltip: item.tooltip,
                deletable: item.deletable,
              }));
              return (
                <CalendarDay
                  key={key}
                  dayNum={d.getDate()}
                  dateLabel={d.toLocaleDateString("de-DE", {
                    weekday: "long",
                    day: "numeric",
                    month: "long",
                    year: "numeric",
                  })}
                  isToday={key === todayKey}
                  inMonth={d.getMonth() === monthIndex}
                  items={dayItems}
                  deleteAction={deleteCalendarEventAction}
                />
              );
            })}
          </div>

          <h2 className="text-sm font-medium text-[var(--rk-ink)] mb-3">Termin manuell hinzufügen</h2>
          {editableCalendars.length === 0 ? (
            <p className="mb-10 text-sm text-[var(--rk-ink-muted)]">
              Lege dir zuerst links einen eigenen Kalender an, dann kannst du hier Termine eintragen.
            </p>
          ) : (
            <NewCalendarEventForm editableCalendars={editableCalendars.map((c) => ({ id: c.id, name: c.name }))} />
          )}

          <h2 className="text-sm font-medium text-[var(--rk-ink)] mb-3">Nächste Termine</h2>
          {upcoming.length === 0 ? (
            <p className="text-sm text-[var(--rk-ink-muted)]">Keine bevorstehenden Termine.</p>
          ) : (
            <div className="flex flex-col gap-2">
              {upcoming.map((item) => {
                const inner = (
                  <div className="flex items-center justify-between gap-3 flex-1 min-w-0">
                    <div className="min-w-0">
                      <p className="text-sm text-[var(--rk-ink)] truncate">{item.title}</p>
                      <p className="text-xs text-[var(--rk-ink-muted)] mt-0.5 truncate">{item.tooltip}</p>
                    </div>
                    <span className="text-xs text-[var(--rk-ink-muted)] shrink-0">
                      {item.start.toLocaleString("de-DE", { day: "2-digit", month: "2-digit", year: "numeric" })}
                      {isMultiDay(item)
                        ? ` – ${(item.end as Date).toLocaleString("de-DE", { day: "2-digit", month: "2-digit", year: "numeric" })}`
                        : `, ${timeLabel(item)}`}
                    </span>
                  </div>
                );
                return (
                  <div
                    key={item.id}
                    className="flex items-center gap-3 bg-[var(--rk-surface)] border border-white/10 rounded-xl px-4 py-3"
                    style={{ borderLeft: `3px solid ${item.color}` }}
                  >
                    {item.href ? (
                      <Link href={item.href} className="flex-1 min-w-0 hover:opacity-80 transition-opacity">
                        {inner}
                      </Link>
                    ) : (
                      inner
                    )}
                    {item.deletable && (
                      <DeleteEventButton eventId={item.id.replace("event-", "")} action={deleteCalendarEventAction} />
                    )}
                  </div>
                );
              })}
            </div>
          )}
        </div>
      </div>
    </main>
  );
}
