"use server";

import crypto from "crypto";
import { createElement } from "react";
import { revalidatePath } from "next/cache";
import { Prisma, UserRole } from "@prisma/client";
import { z } from "zod";

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

const INVITE_TOKEN_TTL_DAYS = 7;
const BASE_URL =
  process.env.NEXTAUTH_URL ?? process.env.AUTH_URL ?? "http://localhost:3000";

const childSchema = z.object({
  firstName: z.string().min(1, "Vorname des Kindes fehlt.").max(100).trim(),
  lastName: z.string().min(1, "Nachname des Kindes fehlt.").max(100).trim(),
});

const parentInputSchema = z.object({
  firstName: z.string().min(1, "Vorname ist erforderlich.").max(100).trim(),
  lastName: z.string().min(1, "Nachname ist erforderlich.").max(100).trim(),
  email: z
    .string()
    .email("Bitte eine gültige E-Mail-Adresse angeben.")
    .toLowerCase()
    .trim(),
});

const addFamilySchema = z.object({
  familyName: z.string().min(1, "Familienname ist erforderlich.").max(120).trim(),
  parent1FirstName: z.string().min(1, "Vorname ist erforderlich.").max(100).trim(),
  parent1LastName: z.string().min(1, "Nachname ist erforderlich.").max(100).trim(),
  parent1Email: z
    .string()
    .email("Bitte eine gültige E-Mail-Adresse angeben.")
    .toLowerCase()
    .trim(),
  parent2FirstName: z.string().max(100).trim().optional(),
  parent2LastName: z.string().max(100).trim().optional(),
  parent2Email: z.string().trim().optional(),
  childCount: z.coerce
    .number()
    .int()
    .min(1, "Mindestens ein Kind erforderlich.")
    .max(10),
});

const updateFamilySchema = z.object({
  familyId: z.string().min(1),
  familyName: z.string().min(1, "Familienname ist erforderlich.").max(120).trim(),
  parents: z.array(
    parentInputSchema.extend({
      id: z.string().min(1),
    }),
  ).min(1).max(2),
  newParents: z.array(parentInputSchema).max(1).default([]),
  children: z
    .array(
      childSchema.extend({
        id: z.string().min(1),
      }),
    )
    .max(20),
  newChildren: z.array(childSchema).max(10).default([]),
  removedChildIds: z.array(z.string().min(1)).max(20).default([]),
});

export type AddFamilyState = {
  errors?: {
    familyName?: string[];
    parent1FirstName?: string[];
    parent1LastName?: string[];
    parent1Email?: string[];
    parent2FirstName?: string[];
    parent2LastName?: string[];
    parent2Email?: string[];
    children?: string[];
    _form?: string[];
  };
  success?: boolean;
};

type InviteJob = {
  to: string;
  parentName: string;
  inviteUrl: string;
};

function optionalParentFromForm(data: z.infer<typeof addFamilySchema>) {
  const firstName = data.parent2FirstName?.trim() ?? "";
  const lastName = data.parent2LastName?.trim() ?? "";
  const email = data.parent2Email?.trim() ?? "";

  if (!firstName && !lastName && !email) {
    return { ok: true as const, parent: null };
  }

  const parsed = parentInputSchema.safeParse({ firstName, lastName, email });
  if (!parsed.success) {
    return {
      ok: false as const,
      errors: {
        parent2FirstName: parsed.error.flatten().fieldErrors.firstName,
        parent2LastName: parsed.error.flatten().fieldErrors.lastName,
        parent2Email: parsed.error.flatten().fieldErrors.email,
      },
    };
  }

  return { ok: true as const, parent: parsed.data };
}

function createInviteToken() {
  const token = crypto.randomUUID();
  const expires = new Date(
    Date.now() + INVITE_TOKEN_TTL_DAYS * 24 * 60 * 60_000,
  );
  return { token, expires, inviteUrl: `${BASE_URL}/invite/${token}` };
}

async function sendInviteJobs(kitaName: string, jobs: InviteJob[]) {
  for (const job of jobs) {
    const result = await sendAppEmail({
      to: job.to,
      subject: `Einladung zu ${kitaName} im Kita-Planer`,
      react: createElement(InviteEmail, {
        parentName: job.parentName,
        kitaName,
        inviteLink: job.inviteUrl,
      }),
    });

    if (!result.success) {
      return result.error;
    }
  }

  return null;
}

export async function addFamilyAction(
  _prev: AddFamilyState,
  formData: FormData,
): Promise<AddFamilyState> {
  const session = await requireRole([UserRole.ADMIN]);
  const kitaId = session.user.kitaId;

  if (!kitaId) {
    return { errors: { _form: ["Kein Mandant zugeordnet."] } };
  }

  const parsedFamily = addFamilySchema.safeParse(Object.fromEntries(formData));
  if (!parsedFamily.success) {
    return { errors: parsedFamily.error.flatten().fieldErrors };
  }

  const optionalParent = optionalParentFromForm(parsedFamily.data);
  if (!optionalParent.ok) {
    return { errors: optionalParent.errors };
  }

  const childrenRaw: z.infer<typeof childSchema>[] = [];
  for (let i = 0; i < parsedFamily.data.childCount; i++) {
    const parsed = childSchema.safeParse({
      firstName: formData.get(`childFirstName_${i}`),
      lastName: formData.get(`childLastName_${i}`),
    });

    if (!parsed.success) {
      return {
        errors: {
          children: [
            `Kind ${i + 1}: ${Object.values(parsed.error.flatten().fieldErrors).flat().join(", ")}`,
          ],
        },
      };
    }
    childrenRaw.push(parsed.data);
  }

  const mailConfigError = getAppEmailConfigError();
  if (mailConfigError) {
    return { errors: { _form: [mailConfigError] } };
  }

  const kita = await prisma.kita.findUnique({
    where: { id: kitaId },
    select: { name: true },
  });

  if (!kita) {
    return { errors: { _form: ["Kita wurde nicht gefunden."] } };
  }

  const parents = [
    {
      firstName: parsedFamily.data.parent1FirstName,
      lastName: parsedFamily.data.parent1LastName,
      email: parsedFamily.data.parent1Email,
    },
    ...(optionalParent.parent ? [optionalParent.parent] : []),
  ];
  const emailSet = new Set(parents.map((parent) => parent.email));
  if (emailSet.size !== parents.length) {
    return {
      errors: {
        _form: ["Die E-Mail-Adressen der Elternteile müssen unterschiedlich sein."],
      },
    };
  }

  const inviteJobs: InviteJob[] = [];

  try {
    await prisma.$transaction(async (tx) => {
      const family = await tx.family.create({
        data: {
          kitaId,
          name: parsedFamily.data.familyName,
        },
      });

      for (const parent of parents) {
        const createdParent = await tx.user.create({
          data: {
            kitaId,
            familyId: family.id,
            email: parent.email,
            firstName: parent.firstName,
            lastName: parent.lastName,
            passwordHash: "",
            role: UserRole.ELTERN,
          },
        });
        const invite = createInviteToken();
        inviteJobs.push({
          to: parent.email,
          parentName: `${parent.firstName} ${parent.lastName}`,
          inviteUrl: invite.inviteUrl,
        });
        await tx.verificationToken.create({
          data: {
            identifier: createdParent.id,
            token: invite.token,
            expires: invite.expires,
          },
        });
      }

      await tx.child.createMany({
        data: childrenRaw.map((child) => ({
          kitaId,
          familyId: family.id,
          firstName: child.firstName,
          lastName: child.lastName,
        })),
      });
    });
  } catch (err) {
    if (
      err instanceof Prisma.PrismaClientKnownRequestError &&
      err.code === "P2002"
    ) {
      return {
        errors: {
          _form: ["Mindestens eine E-Mail-Adresse existiert bereits."],
        },
      };
    }
    throw err;
  }

  const emailError = await sendInviteJobs(kita.name, inviteJobs);
  if (emailError) {
    return {
      errors: {
        _form: [
          `Familie wurde angelegt, aber mindestens eine Einladung konnte nicht versendet werden: ${emailError}`,
        ],
      },
    };
  }

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

export async function updateFamilyAction(rawPayload: unknown) {
  const session = await requireRole([UserRole.ADMIN]);
  const kitaId = session.user.kitaId;

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

  const parsed = updateFamilySchema.safeParse(rawPayload);
  if (!parsed.success) {
    return { error: "Ungültige Eingabedaten." };
  }

  const {
    familyId,
    familyName,
    parents,
    newParents,
    children,
    newChildren,
    removedChildIds,
  } = parsed.data;

  const parentEmails = [...parents, ...newParents].map((parent) => parent.email);
  if (new Set(parentEmails).size !== parentEmails.length) {
    return { error: "Die E-Mail-Adressen der Elternteile müssen unterschiedlich sein." };
  }

  const removedChildIdSet = new Set(removedChildIds);
  const activeChildren = children.filter((child) => !removedChildIdSet.has(child.id));
  if (activeChildren.length + newChildren.length === 0) {
    return { error: "Eine Familie benötigt mindestens ein Kind." };
  }

  if (newParents.length > 0) {
    const mailConfigError = getAppEmailConfigError();
    if (mailConfigError) {
      return { error: mailConfigError };
    }
  }

  const kita = await prisma.kita.findUnique({
    where: { id: kitaId },
    select: { name: true },
  });

  if (!kita) {
    return { error: "Kita wurde nicht gefunden." };
  }

  const inviteJobs: InviteJob[] = [];

  try {
    const existingFamily = await prisma.family.findFirst({
      where: { id: familyId, kitaId },
      select: {
        id: true,
        users: { select: { id: true } },
        children: { select: { id: true } },
      },
    });

    if (!existingFamily) {
      return { error: "Familie wurde nicht gefunden." };
    }

    const existingParentIds = new Set(existingFamily.users.map((user) => user.id));
    const existingChildIds = new Set(existingFamily.children.map((child) => child.id));

    if (!parents.every((parent) => existingParentIds.has(parent.id))) {
      return { error: "Mindestens ein Elternteil gehört nicht zu dieser Familie." };
    }

    if (!children.every((child) => existingChildIds.has(child.id))) {
      return { error: "Mindestens ein Kind gehört nicht zu dieser Familie." };
    }

    if (!removedChildIds.every((childId) => existingChildIds.has(childId))) {
      return { error: "Mindestens ein Kind gehört nicht zu dieser Familie." };
    }

    if (existingParentIds.size + newParents.length > 2) {
      return { error: "Pro Familie sind maximal zwei Elternteile vorgesehen." };
    }

    await prisma.$transaction(async (tx) => {
      await tx.family.update({
        where: { id: familyId },
        data: { name: familyName },
      });

      for (const parent of parents) {
        await tx.user.update({
          where: { id: parent.id },
          data: {
            firstName: parent.firstName,
            lastName: parent.lastName,
            email: parent.email,
          },
        });
      }

      for (const parent of newParents) {
        const createdParent = await tx.user.create({
          data: {
            kitaId,
            familyId,
            email: parent.email,
            firstName: parent.firstName,
            lastName: parent.lastName,
            passwordHash: "",
            role: UserRole.ELTERN,
          },
        });
        const invite = createInviteToken();
        inviteJobs.push({
          to: parent.email,
          parentName: `${parent.firstName} ${parent.lastName}`,
          inviteUrl: invite.inviteUrl,
        });
        await tx.verificationToken.create({
          data: {
            identifier: createdParent.id,
            token: invite.token,
            expires: invite.expires,
          },
        });
      }

      for (const child of activeChildren) {
        await tx.child.update({
          where: { id: child.id },
          data: {
            firstName: child.firstName,
            lastName: child.lastName,
          },
        });
      }

      for (const childId of removedChildIds) {
        await tx.child.delete({ where: { id: childId } });
      }

      for (const child of newChildren) {
        await tx.child.create({
          data: {
            kitaId,
            familyId,
            firstName: child.firstName,
            lastName: child.lastName,
          },
        });
      }
    });
  } catch (error) {
    if (
      error instanceof Prisma.PrismaClientKnownRequestError &&
      error.code === "P2002"
    ) {
      return { error: "Mindestens eine E-Mail-Adresse existiert bereits." };
    }

    console.error("Fehler beim Aktualisieren der Familie:", error);
    return { error: "Familie konnte nicht aktualisiert werden." };
  }

  const emailError = await sendInviteJobs(kita.name, inviteJobs);
  if (emailError) {
    return {
      error: `Familie wurde aktualisiert, aber mindestens eine Einladung konnte nicht versendet werden: ${emailError}`,
    };
  }

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