"use server";

import { access, rm } from "node:fs/promises";
import path from "node:path";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
import { requireSession } from "@/lib/auth/session";
import {
  createNotifications,
  markNotificationsReadByLink,
  upsertLinkNotification,
} from "@/lib/notifications";
import { chatDir, isSafeStoredName } from "@/lib/upload";
import { REACTIONS, DELETE_WINDOW_MS } from "./constants";
import type { AttachmentKind } from "@prisma/client";

const MAX_BODY = 4000;

export type UploadedAttachment = {
  storedName: string;
  kind: AttachmentKind;
  mime: string;
  size: number;
  originalName: string;
  width?: number | null;
  height?: number | null;
  durationMs?: number | null;
};

const VALID_KINDS: AttachmentKind[] = ["IMAGE", "AUDIO", "FILE"];

/** Hilfsfunktion: @-Tokens aus einem Nachrichtentext extrahieren. */
function extractMentionTokens(body: string): string[] {
  const matches = body.matchAll(/@([\p{L}0-9_]+)/gu);
  return [...matches].map((m) => m[1].toLowerCase());
}

async function assertMember(channelId: string, userId: string) {
  const member = await prisma.channelMember.findUnique({
    where: { channelId_userId: { channelId, userId } },
    select: { channelId: true },
  });
  return Boolean(member);
}

export type SendState = { error?: string; ok?: boolean };

/** Nachricht senden — mit Zugriffs- und Rechteprüfung sowie Mention-Logik. */
export async function sendMessageAction(
  channelId: string,
  body: string,
  attachments: UploadedAttachment[] = [],
  replyToId?: string | null,
): Promise<SendState> {
  const session = await requireSession();
  if (!session.user.permissions?.includes("chat.send")) {
    return { error: "Keine Berechtigung zum Senden." };
  }

  const text = body.trim();
  if (text.length > MAX_BODY) return { error: "Nachricht zu lang." };

  if (!(await assertMember(channelId, session.user.id))) {
    return { error: "Kein Zugriff auf diesen Kanal." };
  }

  // Antwort-Bezug validieren (muss zum selben Kanal gehören).
  let validReplyTo: string | null = null;
  if (replyToId) {
    const parent = await prisma.message.findUnique({
      where: { id: replyToId },
      select: { channelId: true },
    });
    if (parent?.channelId === channelId) validReplyTo = replyToId;
  }

  // Anhänge serverseitig validieren: nur Dateien, die wirklich im Kanal-
  // Verzeichnis liegen (Schutz vor manipulierten Pfaden).
  const valid: UploadedAttachment[] = [];
  for (const a of attachments.slice(0, 10)) {
    if (!isSafeStoredName(a.storedName)) continue;
    if (!VALID_KINDS.includes(a.kind)) continue;
    try {
      await access(path.join(chatDir(channelId), a.storedName));
      valid.push(a);
    } catch {
      /* Datei fehlt → ignorieren */
    }
  }

  if (!text && valid.length === 0) {
    return { error: "Leere Nachricht." };
  }

  // Kanalmitglieder für Mention-Auflösung laden.
  const [members, channel] = await Promise.all([
    prisma.channelMember.findMany({
      where: { channelId },
      select: { userId: true, user: { select: { name: true, displayName: true } } },
    }),
    prisma.channel.findUnique({
      where: { id: channelId },
      select: {
        type: true,
        name: true,
        project: { select: { name: true } },
        team: { select: { name: true } },
      },
    }),
  ]);

  const tokens = extractMentionTokens(text);
  const mentionAll = tokens.includes("alle") || tokens.includes("all");
  const mentionedUserIds = members
    .filter((m) => {
      if (m.userId === session.user.id) return false;
      if (mentionAll) return true;
      const first = m.user.name.split(" ")[0]?.toLowerCase();
      const display = (m.user.displayName ?? "").toLowerCase();
      return (first && tokens.includes(first)) || (display && tokens.includes(display));
    })
    .map((m) => m.userId);

  await prisma.$transaction(async (tx) => {
    const msg = await tx.message.create({
      data: { channelId, authorId: session.user.id, body: text, replyToId: validReplyTo },
    });

    if (valid.length > 0) {
      await tx.messageAttachment.createMany({
        data: valid.map((a) => ({
          messageId: msg.id,
          kind: a.kind,
          storedName: a.storedName,
          mime: a.mime,
          size: a.size,
          originalName: a.originalName.slice(0, 255),
          width: a.width ?? null,
          height: a.height ?? null,
          durationMs: a.durationMs ?? null,
        })),
      });
    }

    if (mentionedUserIds.length > 0) {
      await tx.messageMention.createMany({
        data: mentionedUserIds.map((userId) => ({ messageId: msg.id, userId })),
      });
    }

    // Sender hat seinen eigenen Stand gelesen.
    await tx.channelMember.update({
      where: { channelId_userId: { channelId, userId: session.user.id } },
      data: { lastReadAt: new Date() },
    });
  });

  // ── Benachrichtigungen ──
  const authorName = session.user.name ?? "Jemand";
  const link = `/chat/${channelId}`;
  const preview = text.trim()
    ? text.slice(0, 120)
    : valid[0]
      ? { IMAGE: "📷 Bild", AUDIO: "🎤 Sprachnachricht", FILE: "📎 Datei" }[valid[0].kind]
      : "Neue Nachricht";

  if (mentionedUserIds.length > 0) {
    await createNotifications(mentionedUserIds, {
      type: "MENTION",
      title: `${authorName} hat dich erwähnt`,
      body: preview,
      link,
    });
  }

  // Alle übrigen Mitglieder über die neue Nachricht informieren — je Kanal
  // zusammengefasst, damit die Glocke nicht von einem Verlauf geflutet wird.
  const mentioned = new Set(mentionedUserIds);
  const otherMemberIds = members
    .map((m) => m.userId)
    .filter((id) => id !== session.user.id && !mentioned.has(id));

  if (otherMemberIds.length > 0) {
    const channelName =
      channel?.name ?? channel?.project?.name ?? channel?.team?.name ?? null;
    await upsertLinkNotification(otherMemberIds, {
      type: "GENERIC",
      title:
        channel?.type === "DIRECT" || !channelName
          ? `Neue Nachricht von ${authorName}`
          : `${authorName} in ${channelName}`,
      body: preview,
      link,
    });
  }

  revalidatePath("/chat", "layout");
  return { ok: true };
}

/** Kanal als gelesen markieren (inkl. offener Kanal-Benachrichtigungen). */
export async function markChannelReadAction(channelId: string): Promise<void> {
  const session = await requireSession();
  if (!(await assertMember(channelId, session.user.id))) return;
  await Promise.all([
    prisma.channelMember.update({
      where: { channelId_userId: { channelId, userId: session.user.id } },
      data: { lastReadAt: new Date() },
    }),
    markNotificationsReadByLink(session.user.id, `/chat/${channelId}`),
  ]);
}

/** Reaktion umschalten (hinzufügen/entfernen). */
export async function toggleReactionAction(
  messageId: string,
  emoji: string,
): Promise<SendState> {
  const session = await requireSession();
  if (!session.user.permissions?.includes("chat.send")) {
    return { error: "Keine Berechtigung." };
  }
  if (!REACTIONS.includes(emoji as (typeof REACTIONS)[number])) {
    return { error: "Ungültige Reaktion." };
  }

  const msg = await prisma.message.findUnique({
    where: { id: messageId },
    select: { channelId: true },
  });
  if (!msg || !(await assertMember(msg.channelId, session.user.id))) {
    return { error: "Kein Zugriff." };
  }

  const existing = await prisma.messageReaction.findUnique({
    where: { messageId_userId_emoji: { messageId, userId: session.user.id, emoji } },
    select: { id: true },
  });
  if (existing) {
    await prisma.messageReaction.delete({ where: { id: existing.id } });
  } else {
    await prisma.messageReaction.create({
      data: { messageId, userId: session.user.id, emoji },
    });
  }
  return { ok: true };
}

/** Nachricht fixieren / lösen. */
export async function pinMessageAction(
  messageId: string,
  pin: boolean,
): Promise<SendState> {
  const session = await requireSession();
  if (!session.user.permissions?.includes("chat.send")) {
    return { error: "Keine Berechtigung." };
  }
  const msg = await prisma.message.findUnique({
    where: { id: messageId },
    select: { channelId: true },
  });
  if (!msg || !(await assertMember(msg.channelId, session.user.id))) {
    return { error: "Kein Zugriff." };
  }
  await prisma.message.update({
    where: { id: messageId },
    data: {
      pinnedAt: pin ? new Date() : null,
      pinnedById: pin ? session.user.id : null,
    },
  });
  revalidatePath("/chat", "layout");
  return { ok: true };
}

/** Eigene Nachricht innerhalb von 10 Minuten für alle löschen (Soft-Delete). */
export async function deleteOwnMessageAction(messageId: string): Promise<SendState> {
  const session = await requireSession();
  const msg = await prisma.message.findUnique({
    where: { id: messageId },
    select: { authorId: true, createdAt: true, deletedAt: true },
  });
  if (!msg) return { error: "Nicht gefunden." };
  if (msg.authorId !== session.user.id) return { error: "Nur eigene Nachrichten." };
  if (msg.deletedAt) return { ok: true };
  if (Date.now() - msg.createdAt.getTime() > DELETE_WINDOW_MS) {
    return { error: "Löschfrist (10 Min.) abgelaufen." };
  }
  await prisma.message.update({
    where: { id: messageId },
    data: { deletedAt: new Date(), body: "", pinnedAt: null },
  });
  return { ok: true };
}

/**
 * Gruppe löschen — inkl. Nachrichten, Anhängen und Mitgliedschaften.
 * Erlaubt für die Ersteller:in der Gruppe sowie für Administrator:innen.
 * Projekt-/Teamkanäle bleiben an ihr Projekt bzw. Team gebunden.
 */
export async function deleteChannelAction(channelId: string): Promise<SendState> {
  const session = await requireSession();

  const channel = await prisma.channel.findUnique({
    where: { id: channelId },
    select: { id: true, type: true, createdById: true },
  });
  if (!channel) return { error: "Kanal nicht gefunden." };

  if (!(await assertMember(channelId, session.user.id))) {
    return { error: "Kein Zugriff auf diesen Kanal." };
  }
  if (channel.type !== "GROUP") {
    return {
      error:
        channel.type === "DIRECT"
          ? "Direktnachrichten können nicht gelöscht werden."
          : "Projekt- und Teamkanäle können hier nicht gelöscht werden.",
    };
  }

  const isAdmin = Boolean(session.user.permissions?.includes("admin.access"));
  if (channel.createdById !== session.user.id && !isAdmin) {
    return { error: "Nur die Ersteller:in oder ein Admin kann die Gruppe löschen." };
  }

  // Datenbank räumt per Cascade auf (Nachrichten, Anhänge, Mitglieder).
  await prisma.channel.delete({ where: { id: channelId } });

  // Hochgeladene Dateien des Kanals ebenfalls entfernen.
  await rm(chatDir(channelId), { recursive: true, force: true }).catch(() => {});

  revalidatePath("/chat", "layout");
  return { ok: true };
}

export type CreateChannelInput = {
  type: "GROUP" | "DIRECT";
  name?: string;
  memberIds: string[];
};

/** Neuen Kanal (Gruppe oder Direktnachricht) anlegen. */
export async function createChannelAction(input: CreateChannelInput): Promise<void> {
  const session = await requireSession();
  if (!session.user.permissions?.includes("chat.create_channel")) {
    redirect("/chat");
  }
  const me = session.user.id;

  // Eingaben säubern & nur existierende, aktive Nutzer zulassen.
  const requested = [...new Set(input.memberIds)].filter((id) => id && id !== me);
  const validUsers = await prisma.user.findMany({
    where: { id: { in: requested.length ? requested : ["__none__"] }, status: "ACTIVE" },
    select: { id: true },
  });
  const memberIds = validUsers.map((u) => u.id);

  let targetId: string;

  if (input.type === "DIRECT") {
    if (memberIds.length !== 1) redirect("/chat");
    const other = memberIds[0];

    // Bestehende DM zwischen beiden wiederverwenden.
    const existing = await prisma.channel.findFirst({
      where: {
        type: "DIRECT",
        AND: [
          { members: { some: { userId: me } } },
          { members: { some: { userId: other } } },
        ],
      },
      select: { id: true, members: { select: { userId: true } } },
    });
    const exactDm = existing && existing.members.length === 2 ? existing : null;

    if (exactDm) {
      targetId = exactDm.id;
    } else {
      const channel = await prisma.channel.create({
        data: {
          type: "DIRECT",
          createdById: me,
          members: { create: [{ userId: me }, { userId: other }] },
        },
        select: { id: true },
      });
      targetId = channel.id;
    }
  } else {
    const name = (input.name ?? "").trim().slice(0, 160) || "Neuer Kanal";
    const allMembers = [...new Set([me, ...memberIds])];
    const channel = await prisma.channel.create({
      data: {
        type: "GROUP",
        name,
        createdById: me,
        members: { create: allMembers.map((userId) => ({ userId })) },
      },
      select: { id: true },
    });
    targetId = channel.id;
  }

  revalidatePath("/chat", "layout");
  redirect(`/chat/${targetId}`);
}
