"use server";

import { randomUUID } from "crypto";
import { mkdir, rm, writeFile } from "fs/promises";
import path from "path";
import { createElement } from "react";
import { revalidatePath } from "next/cache";
import { Prisma, UserRole } from "@prisma/client";
import { z } from "zod";

import { NewsEmail } from "@/emails/NewsEmail";
import { requireKitaSession, requireRole } from "@/lib/auth-utils";
import { getAppEmailConfigError, sendAppEmail } from "@/lib/mail";
import { prisma } from "@/lib/prisma";

const MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024;
const ALLOWED_TYPES = new Map([
  ["application/pdf", ".pdf"],
  ["image/jpeg", ".jpg"],
  ["image/png", ".png"],
]);

const ALLOWED_EXTENSIONS = new Set([".pdf", ".jpg", ".jpeg", ".png"]);

const audienceSchema = z.enum(["ELTERN", "ERZIEHER", "ALLE"]);

const announcementSchema = z.object({
  title: z.string().trim().min(3).max(160),
  content: z.string().trim().min(3).max(20_000),
  sendEmail: z.boolean(),
  audience: audienceSchema,
});

function getUploadDir() {
  return path.resolve(process.env.UPLOAD_DIR ?? "uploads", "announcements");
}

function getSafeFileName(name: string) {
  return name.replace(/[^\w.\- äöüÄÖÜß]/g, "_").slice(0, 160);
}

function validateAttachment(file: File) {
  const extension = path.extname(file.name).toLowerCase();
  const typeAllowed = ALLOWED_TYPES.has(file.type);
  const extensionAllowed = ALLOWED_EXTENSIONS.has(extension);

  if (!typeAllowed && !extensionAllowed) {
    return "Nur PDF, JPG und PNG sind erlaubt.";
  }

  if (file.size > MAX_ATTACHMENT_SIZE) {
    return "Anhänge dürfen maximal 10 MB groß sein.";
  }

  return null;
}

async function getRecipients(kitaId: string, audience: z.infer<typeof audienceSchema>) {
  const where: Prisma.UserWhereInput =
    audience === "ELTERN"
      ? {
          kitaId,
          familyId: { not: null },
          emailVerifiedAt: { not: null },
        }
      : audience === "ERZIEHER"
        ? {
            kitaId,
            role: UserRole.ERZIEHER,
            emailVerifiedAt: { not: null },
          }
        : {
            kitaId,
            emailVerifiedAt: { not: null },
            OR: [{ familyId: { not: null } }, { role: UserRole.ERZIEHER }],
          };

  const users = await prisma.user.findMany({
    where,
    select: { email: true },
  });

  return Array.from(new Set(users.map((user) => user.email)));
}

export async function createAnnouncement(formData: FormData) {
  const session = await requireRole([UserRole.ADMIN]);
  const kitaId = session.user.kitaId;

  if (!kitaId) {
    return { error: "Kein Mandant zugeordnet." };
  }

  const parsed = announcementSchema.safeParse({
    title: formData.get("title"),
    content: formData.get("content"),
    sendEmail: formData.get("sendEmail") === "on",
    audience: formData.get("audience") || "ELTERN",
  });

  if (!parsed.success) {
    return { error: "Bitte Titel und Inhalt ausfüllen." };
  }

  const attachmentFiles = formData
    .getAll("attachments")
    .filter((value): value is File => value instanceof File && value.size > 0);

  for (const file of attachmentFiles) {
    const error = validateAttachment(file);
    if (error) {
      return { error };
    }
  }

  const attachmentInputs = attachmentFiles.map((file) => {
    const id = randomUUID();
    return {
      id,
      file,
      fileName: getSafeFileName(file.name),
      fileType: file.type || "application/octet-stream",
      fileUrl: `/api/announcements/attachments/${id}`,
    };
  });

  const announcement = await prisma.announcement.create({
    data: {
      kitaId,
      title: parsed.data.title,
      content: parsed.data.content,
      authorId: session.user.id,
      attachments: {
        create: attachmentInputs.map((attachment) => ({
          id: attachment.id,
          fileName: attachment.fileName,
          fileType: attachment.fileType,
          fileUrl: attachment.fileUrl,
        })),
      },
    },
    select: {
      id: true,
      title: true,
      content: true,
    },
  });

  if (attachmentInputs.length > 0) {
    const uploadDir = getUploadDir();
    try {
      await mkdir(uploadDir, { recursive: true });
      await Promise.all(
        attachmentInputs.map(async (attachment) => {
          const arrayBuffer = await attachment.file.arrayBuffer();
          await writeFile(
            path.join(uploadDir, attachment.id),
            Buffer.from(arrayBuffer),
          );
        }),
      );
    } catch (error) {
      await prisma.announcement.delete({ where: { id: announcement.id } });
      await Promise.all(
        attachmentInputs.map((attachment) =>
          rm(path.join(uploadDir, attachment.id), { force: true }),
        ),
      );
      console.error("Ankündigungs-Anhang konnte nicht gespeichert werden:", error);
      return { error: "Anhang konnte nicht gespeichert werden." };
    }
  }

  let emailError: string | null = null;
  if (parsed.data.sendEmail) {
    const configError = getAppEmailConfigError();
    if (configError) {
      emailError = configError;
    } else {
      const recipients = await getRecipients(kitaId, parsed.data.audience);
      if (recipients.length > 0) {
        const result = await sendAppEmail({
          to: recipients,
          subject: `Neue Kita-Ankündigung: ${announcement.title}`,
          react: createElement(NewsEmail, {
            title: announcement.title,
            content: announcement.content,
            dashboardUrl: `${process.env.NEXTAUTH_URL ?? process.env.AUTH_URL ?? "http://localhost:3000"}/dashboard`,
          }),
        });

        if (!result.success) {
          emailError = result.error;
        }
      }
    }
  }

  revalidatePath("/dashboard");
  revalidatePath("/dashboard/admin/news");

  if (emailError) {
    return {
      success: true,
      warning: `Ankündigung gespeichert, E-Mail-Versand fehlgeschlagen: ${emailError}`,
    };
  }

  return { success: true };
}

export async function markAnnouncementRead(announcementId: string) {
  const session = await requireKitaSession();

  const announcement = await prisma.announcement.findFirst({
    where: {
      id: announcementId,
      kitaId: session.user.kitaId,
    },
    select: { id: true },
  });

  if (!announcement) {
    return { error: "Ankündigung wurde nicht gefunden." };
  }

  await prisma.announcementRead.upsert({
    where: {
      userId_announcementId: {
        userId: session.user.id,
        announcementId: announcement.id,
      },
    },
    create: {
      userId: session.user.id,
      announcementId: announcement.id,
    },
    update: {
      readAt: new Date(),
    },
  });

  revalidatePath("/dashboard");
  return { success: true };
}
