"use server";

import { randomBytes } from "node:crypto";
import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
import { requireSession } from "@/lib/auth/session";
import { hashPassword } from "@/lib/auth/password";
import { sendMail } from "@/lib/mail/mailer";
import {
  getOnboardingSettings,
  resolveMailFrom,
  setMailSettings,
  setOnboardingSettings,
  setAbsenceTypes,
  setNewModules,
  type MailSettings,
  type OnboardingSettings,
  type AbsenceTypeDef,
} from "@/lib/settings";
import { PERMISSIONS, type PermissionKey } from "@/lib/rbac/catalog";

const VALID_PERMISSIONS = new Set(Object.keys(PERMISSIONS));
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

async function guard(permission: PermissionKey): Promise<string | null> {
  const session = await requireSession();
  return session.user.permissions?.includes(permission) ? session.user.id : null;
}

function generatePassword(): string {
  const chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
  const bytes = randomBytes(12);
  let s = "";
  for (const b of bytes) s += chars[b % chars.length];
  return `Av-${s.slice(0, 4)}-${s.slice(4, 8)}!${s.slice(8, 12)}`;
}

function slugKey(name: string, fallback: string): string {
  const base = name
    .toLowerCase()
    .normalize("NFKD")
    .replace(/[^\w\s-]/g, "")
    .trim()
    .replace(/\s+/g, "_")
    .slice(0, 40);
  return base || fallback;
}

async function sendWelcomeMail(to: string, name: string, password: string) {
  const from = await resolveMailFrom();
  const loginUrl = `${process.env.APP_URL ?? "http://localhost:3000"}/login`;
  await sendMail({
    from,
    to,
    subject: "Willkommen im Avaria Team OS",
    text:
      `Hallo ${name},\n\ndein Zugang zum Avaria Team OS wurde eingerichtet.\n\n` +
      `Login: ${loginUrl}\nE-Mail: ${to}\nPasswort: ${password}\n\n` +
      `Bitte ändere dein Passwort nach dem ersten Login.`,
    html:
      `<p>Hallo ${name},</p><p>dein Zugang zum <b>Avaria Team OS</b> wurde eingerichtet.</p>` +
      `<p><b>Login:</b> <a href="${loginUrl}">${loginUrl}</a><br/>` +
      `<b>E-Mail:</b> ${to}<br/><b>Passwort:</b> <code>${password}</code></p>` +
      `<p>Bitte ändere dein Passwort nach dem ersten Login.</p>`,
  });
}

// ── Nutzer ────────────────────────────────────────────────────────────────
export type UserResult = { error?: string; ok?: boolean; password?: string };

export async function createUserAction(input: {
  name: string;
  email: string;
  jobTitle?: string;
  roleIds: string[];
  supervisorId?: string | null;
  vacationDaysPerYear?: number;
}): Promise<UserResult> {
  const actorId = await guard("admin.users");
  if (!actorId) return { error: "Keine Berechtigung." };

  const name = input.name.trim();
  const email = input.email.trim().toLowerCase();
  if (!name) return { error: "Name erforderlich." };
  if (!EMAIL_RE.test(email)) return { error: "Ungültige E-Mail-Adresse." };

  const exists = await prisma.user.findUnique({ where: { email }, select: { id: true } });
  if (exists) return { error: "E-Mail ist bereits vergeben." };

  const onboarding = await getOnboardingSettings();
  const password =
    onboarding.passwordMode === "fixed" && onboarding.defaultPassword
      ? onboarding.defaultPassword
      : generatePassword();
  const passwordHash = await hashPassword(password);

  const roleIds = await validRoleIds(input.roleIds);

  // Neue Konten starten als INVITED und werden beim ersten Login automatisch aktiv.
  const supervisorId = input.supervisorId
    ? (await prisma.user.findUnique({ where: { id: input.supervisorId }, select: { id: true } }))?.id ?? null
    : null;
  const user = await prisma.user.create({
    data: {
      name: name.slice(0, 120),
      email: email.slice(0, 190),
      jobTitle: input.jobTitle?.trim().slice(0, 120) || null,
      passwordHash,
      status: "INVITED",
      supervisorId,
      vacationDaysPerYear: Math.max(0, Math.min(365, Math.round(input.vacationDaysPerYear ?? 30))),
      roles: { create: roleIds.map((roleId) => ({ roleId })) },
    },
    select: { id: true },
  });

  await prisma.auditLog.create({
    data: { userId: actorId, action: "admin.user_create", entity: "User", entityId: user.id },
  });

  try {
    await sendWelcomeMail(email, name, password);
  } catch {
    /* Mail-Fehler nicht fatal — Passwort wird dem Admin angezeigt. */
  }

  revalidatePath("/admin/users");
  return { ok: true, password };
}

export async function updateUserAction(
  userId: string,
  patch: {
    name?: string;
    email?: string;
    jobTitle?: string | null;
    supervisorId?: string | null;
    vacationDaysPerYear?: number;
  },
): Promise<UserResult> {
  const actorId = await guard("admin.users");
  if (!actorId) return { error: "Keine Berechtigung." };

  const data: Record<string, unknown> = {};
  if (patch.name !== undefined) {
    const n = patch.name.trim();
    if (!n) return { error: "Name erforderlich." };
    data.name = n.slice(0, 120);
  }
  if (patch.email !== undefined) {
    const e = patch.email.trim().toLowerCase();
    if (!EMAIL_RE.test(e)) return { error: "Ungültige E-Mail-Adresse." };
    const other = await prisma.user.findFirst({ where: { email: e, id: { not: userId } }, select: { id: true } });
    if (other) return { error: "E-Mail ist bereits vergeben." };
    data.email = e.slice(0, 190);
  }
  if (patch.jobTitle !== undefined) data.jobTitle = patch.jobTitle?.trim().slice(0, 120) || null;
  if (patch.supervisorId !== undefined) {
    // Kein Selbst-Vorgesetzter.
    data.supervisorId = patch.supervisorId && patch.supervisorId !== userId ? patch.supervisorId : null;
  }
  if (patch.vacationDaysPerYear !== undefined) {
    data.vacationDaysPerYear = Math.max(0, Math.min(365, Math.round(patch.vacationDaysPerYear)));
  }

  await prisma.user.update({ where: { id: userId }, data });
  revalidatePath("/admin/users");
  return { ok: true };
}

/** Sperrt bzw. entsperrt ein Konto (Status SUSPENDED ↔ ACTIVE). */
export async function setUserBlockedAction(userId: string, blocked: boolean): Promise<UserResult> {
  const actorId = await guard("admin.users");
  if (!actorId) return { error: "Keine Berechtigung." };
  if (blocked && userId === actorId) return { error: "Du kannst dich nicht selbst sperren." };
  if (blocked && (await wouldRemoveLastAdmin(userId, []))) {
    return { error: "Die letzte Person mit Admin-Rolle kann nicht gesperrt werden." };
  }
  await prisma.user.update({
    where: { id: userId },
    data: { status: blocked ? "SUSPENDED" : "ACTIVE" },
  });
  await prisma.auditLog.create({
    data: {
      userId: actorId,
      action: blocked ? "admin.user_block" : "admin.user_unblock",
      entity: "User",
      entityId: userId,
    },
  });
  revalidatePath("/admin/users");
  return { ok: true };
}

export async function setUserRolesAction(userId: string, roleIds: string[]): Promise<UserResult> {
  const actorId = await guard("admin.users");
  if (!actorId) return { error: "Keine Berechtigung." };

  const valid = await validRoleIds(roleIds);
  // Verhindern, dass die letzte Admin-Rolle entzogen wird.
  if (await wouldRemoveLastAdmin(userId, valid)) {
    return { error: "Die letzte Person mit Admin-Rolle kann nicht herabgestuft werden." };
  }

  await prisma.$transaction([
    prisma.userRole.deleteMany({ where: { userId } }),
    ...valid.map((roleId) => prisma.userRole.create({ data: { userId, roleId } })),
  ]);
  revalidatePath("/admin/users");
  return { ok: true };
}

export async function resetUserPasswordAction(userId: string): Promise<UserResult> {
  const actorId = await guard("admin.users");
  if (!actorId) return { error: "Keine Berechtigung." };

  const user = await prisma.user.findUnique({ where: { id: userId }, select: { email: true, name: true } });
  if (!user) return { error: "Nutzer nicht gefunden." };

  const password = generatePassword();
  await prisma.user.update({ where: { id: userId }, data: { passwordHash: await hashPassword(password) } });
  await prisma.auditLog.create({
    data: { userId: actorId, action: "admin.user_reset_pw", entity: "User", entityId: userId },
  });
  try {
    await sendWelcomeMail(user.email, user.name, password);
  } catch {
    /* ignore */
  }
  return { ok: true, password };
}

export async function deleteUserAction(userId: string): Promise<UserResult> {
  const actorId = await guard("admin.users");
  if (!actorId) return { error: "Keine Berechtigung." };
  if (userId === actorId) return { error: "Du kannst dich nicht selbst löschen." };
  if (await wouldRemoveLastAdmin(userId, [])) {
    return { error: "Die letzte Person mit Admin-Rolle kann nicht gelöscht werden." };
  }
  await prisma.user.delete({ where: { id: userId } });
  revalidatePath("/admin/users");
  return { ok: true };
}

async function validRoleIds(ids: string[]): Promise<string[]> {
  if (!ids?.length) return [];
  const rows = await prisma.role.findMany({ where: { id: { in: ids } }, select: { id: true } });
  return rows.map((r) => r.id);
}

/** True, wenn nach `nextRoleIds` niemand mehr die ADMIN-Rolle hätte. */
async function wouldRemoveLastAdmin(userId: string, nextRoleIds: string[]): Promise<boolean> {
  const adminRole = await prisma.role.findUnique({ where: { key: "ADMIN" }, select: { id: true } });
  if (!adminRole) return false;
  const userHasAdmin = await prisma.userRole.findFirst({
    where: { userId, roleId: adminRole.id },
    select: { userId: true },
  });
  if (!userHasAdmin) return false; // Nutzer ist kein Admin → unkritisch
  if (nextRoleIds.includes(adminRole.id)) return false; // behält Admin
  const adminCount = await prisma.userRole.count({ where: { roleId: adminRole.id } });
  return adminCount <= 1;
}

// ── Rollen ──────────────────────────────────────────────────────────────────
export type RoleResult = { error?: string; ok?: boolean };

function cleanPermissionKeys(keys: string[]): string[] {
  return Array.from(new Set(keys.filter((k) => VALID_PERMISSIONS.has(k))));
}

async function applyRolePermissions(roleId: string, keys: string[]) {
  const perms = await prisma.permission.findMany({
    where: { key: { in: cleanPermissionKeys(keys) } },
    select: { id: true },
  });
  await prisma.$transaction([
    prisma.rolePermission.deleteMany({ where: { roleId } }),
    ...perms.map((p) => prisma.rolePermission.create({ data: { roleId, permissionId: p.id } })),
  ]);
}

export async function createRoleAction(input: {
  name: string;
  description?: string;
  permissionKeys: string[];
}): Promise<RoleResult> {
  const actorId = await guard("admin.roles");
  if (!actorId) return { error: "Keine Berechtigung." };
  const name = input.name.trim();
  if (!name) return { error: "Name erforderlich." };

  let key = slugKey(name, `role_${Date.now()}`).toUpperCase();
  if (await prisma.role.findUnique({ where: { key }, select: { key: true } })) {
    key = `${key}_${randomBytes(2).toString("hex").toUpperCase()}`;
  }

  const role = await prisma.role.create({
    data: {
      key,
      name: name.slice(0, 120),
      description: input.description?.trim().slice(0, 255) || null,
      isSystem: false,
      rank: 60,
    },
    select: { id: true },
  });
  await applyRolePermissions(role.id, input.permissionKeys);
  revalidatePath("/admin/roles");
  return { ok: true };
}

export async function updateRoleAction(
  roleId: string,
  patch: { name?: string; description?: string | null; permissionKeys?: string[] },
): Promise<RoleResult> {
  const actorId = await guard("admin.roles");
  if (!actorId) return { error: "Keine Berechtigung." };

  const role = await prisma.role.findUnique({ where: { id: roleId }, select: { key: true } });
  if (!role) return { error: "Rolle nicht gefunden." };

  const data: Record<string, unknown> = {};
  if (patch.name !== undefined) {
    const n = patch.name.trim();
    if (!n) return { error: "Name erforderlich." };
    data.name = n.slice(0, 120);
  }
  if (patch.description !== undefined) data.description = patch.description?.trim().slice(0, 255) || null;
  if (Object.keys(data).length) await prisma.role.update({ where: { id: roleId }, data });

  // Admin-Rolle behält immer alle Rechte.
  if (patch.permissionKeys !== undefined && role.key !== "ADMIN") {
    await applyRolePermissions(roleId, patch.permissionKeys);
  }
  revalidatePath("/admin/roles");
  return { ok: true };
}

export async function deleteRoleAction(roleId: string): Promise<RoleResult> {
  const actorId = await guard("admin.roles");
  if (!actorId) return { error: "Keine Berechtigung." };
  const role = await prisma.role.findUnique({
    where: { id: roleId },
    select: { key: true },
  });
  if (!role) return { error: "Rolle nicht gefunden." };
  // Nur die Admin-Rolle ist geschützt (Schutz vor Aussperrung); alle anderen
  // (auch Standard-/System-Rollen) sind löschbar. Zuweisungen per Cascade.
  if (role.key === "ADMIN") return { error: "Die Admin-Rolle kann nicht gelöscht werden." };
  await prisma.role.delete({ where: { id: roleId } });
  revalidatePath("/admin/roles");
  return { ok: true };
}

// ── Teams ─────────────────────────────────────────────────────────────────
export type TeamResult = { error?: string; ok?: boolean; id?: string };

async function syncTeamChannel(teamId: string, name: string, memberIds: string[], createdById: string) {
  let channel = await prisma.channel.findFirst({
    where: { teamId, type: "TEAM" },
    select: { id: true },
  });
  if (!channel) {
    await prisma.channel.create({
      data: {
        type: "TEAM",
        name: `Team ${name}`.slice(0, 160),
        teamId,
        createdById,
        members: { create: memberIds.map((userId) => ({ userId })) },
      },
    });
    return;
  }
  await prisma.channel.update({ where: { id: channel.id }, data: { name: `Team ${name}`.slice(0, 160) } });
  const existing = await prisma.channelMember.findMany({
    where: { channelId: channel.id },
    select: { userId: true },
  });
  const have = new Set(existing.map((e) => e.userId));
  const want = new Set(memberIds);
  const toAdd = memberIds.filter((id) => !have.has(id));
  const toRemove = [...have].filter((id) => !want.has(id));
  await prisma.$transaction([
    ...toAdd.map((userId) => prisma.channelMember.create({ data: { channelId: channel!.id, userId } })),
    ...(toRemove.length
      ? [prisma.channelMember.deleteMany({ where: { channelId: channel!.id, userId: { in: toRemove } } })]
      : []),
  ]);
}

async function validUserIds(ids: string[]): Promise<string[]> {
  if (!ids?.length) return [];
  const rows = await prisma.user.findMany({ where: { id: { in: ids } }, select: { id: true } });
  return rows.map((r) => r.id);
}

export async function createTeamAction(input: {
  name: string;
  description?: string;
  color?: string | null;
  memberIds: string[];
  boardIds: string[];
}): Promise<TeamResult> {
  const actorId = await guard("admin.teams");
  if (!actorId) return { error: "Keine Berechtigung." };
  const name = input.name.trim();
  if (!name) return { error: "Name erforderlich." };

  let key = slugKey(name, `team_${Date.now()}`);
  if (await prisma.team.findUnique({ where: { key }, select: { key: true } })) {
    key = `${key}_${randomBytes(2).toString("hex")}`;
  }

  const memberIds = await validUserIds(input.memberIds);
  const team = await prisma.team.create({
    data: {
      key,
      name: name.slice(0, 120),
      description: input.description?.trim().slice(0, 500) || null,
      color: input.color?.slice(0, 16) || null,
      members: { create: memberIds.map((userId) => ({ userId })) },
    },
    select: { id: true },
  });

  if (input.boardIds?.length) {
    const validBoards = await prisma.board.findMany({
      where: { id: { in: input.boardIds } },
      select: { id: true },
    });
    await prisma.boardTeam.createMany({
      data: validBoards.map((b) => ({ boardId: b.id, teamId: team.id })),
      skipDuplicates: true,
    });
  }
  await syncTeamChannel(team.id, name, memberIds, actorId);

  revalidatePath("/admin/teams");
  return { ok: true, id: team.id };
}

export async function updateTeamAction(
  teamId: string,
  patch: { name?: string; description?: string | null; color?: string | null },
): Promise<TeamResult> {
  const actorId = await guard("admin.teams");
  if (!actorId) return { error: "Keine Berechtigung." };
  const data: Record<string, unknown> = {};
  if (patch.name !== undefined) {
    const n = patch.name.trim();
    if (!n) return { error: "Name erforderlich." };
    data.name = n.slice(0, 120);
  }
  if (patch.description !== undefined) data.description = patch.description?.trim().slice(0, 500) || null;
  if (patch.color !== undefined) data.color = patch.color?.slice(0, 16) || null;
  await prisma.team.update({ where: { id: teamId }, data });

  if (patch.name !== undefined) {
    const members = await prisma.teamMember.findMany({ where: { teamId }, select: { userId: true } });
    await syncTeamChannel(teamId, patch.name.trim(), members.map((m) => m.userId), actorId);
  }
  revalidatePath("/admin/teams");
  return { ok: true };
}

export async function setTeamMembersAction(teamId: string, memberIds: string[]): Promise<TeamResult> {
  const actorId = await guard("admin.teams");
  if (!actorId) return { error: "Keine Berechtigung." };
  const team = await prisma.team.findUnique({ where: { id: teamId }, select: { name: true } });
  if (!team) return { error: "Team nicht gefunden." };

  const valid = await validUserIds(memberIds);
  const existing = await prisma.teamMember.findMany({ where: { teamId }, select: { userId: true } });
  const have = new Set(existing.map((e) => e.userId));
  const want = new Set(valid);
  const toAdd = valid.filter((id) => !have.has(id));
  const toRemove = [...have].filter((id) => !want.has(id));

  await prisma.$transaction([
    ...toAdd.map((userId) => prisma.teamMember.create({ data: { teamId, userId } })),
    ...(toRemove.length
      ? [prisma.teamMember.deleteMany({ where: { teamId, userId: { in: toRemove } } })]
      : []),
  ]);
  await syncTeamChannel(teamId, team.name, valid, actorId);
  revalidatePath("/admin/teams");
  return { ok: true };
}

export async function setTeamBoardsAction(teamId: string, boardIds: string[]): Promise<TeamResult> {
  const actorId = await guard("admin.teams");
  if (!actorId) return { error: "Keine Berechtigung." };
  const valid = boardIds?.length
    ? (await prisma.board.findMany({ where: { id: { in: boardIds } }, select: { id: true } })).map((b) => b.id)
    : [];
  // Nur die Zuordnungen DIESES Teams neu setzen — ein Board kann mehreren Teams
  // zugeordnet sein (Mehrfach-Freischaltung).
  await prisma.$transaction([
    prisma.boardTeam.deleteMany({ where: { teamId } }),
    ...(valid.length
      ? [prisma.boardTeam.createMany({ data: valid.map((boardId) => ({ boardId, teamId })), skipDuplicates: true })]
      : []),
  ]);
  revalidatePath("/admin/teams");
  return { ok: true };
}

export async function deleteTeamAction(teamId: string): Promise<TeamResult> {
  const actorId = await guard("admin.teams");
  if (!actorId) return { error: "Keine Berechtigung." };
  await prisma.team.delete({ where: { id: teamId } });
  revalidatePath("/admin/teams");
  return { ok: true };
}

// ── Einstellungen ────────────────────────────────────────────────────────────
export async function saveMailSettingsAction(v: MailSettings): Promise<{ error?: string; ok?: boolean }> {
  const actorId = await guard("admin.settings");
  if (!actorId) return { error: "Keine Berechtigung." };
  if (!EMAIL_RE.test(v.fromEmail.trim())) return { error: "Ungültige Absender-Adresse." };
  await setMailSettings(v);
  revalidatePath("/admin/settings");
  return { ok: true };
}

export async function saveOnboardingSettingsAction(
  v: OnboardingSettings,
): Promise<{ error?: string; ok?: boolean }> {
  const actorId = await guard("admin.settings");
  if (!actorId) return { error: "Keine Berechtigung." };
  if (v.passwordMode === "fixed" && v.defaultPassword.trim().length < 8) {
    return { error: "Festes Passwort muss mindestens 8 Zeichen haben." };
  }
  await setOnboardingSettings(v);
  revalidatePath("/admin/settings");
  return { ok: true };
}

export async function saveAbsenceTypesAction(
  types: AbsenceTypeDef[],
): Promise<{ error?: string; ok?: boolean }> {
  const actorId = await guard("admin.settings");
  if (!actorId) return { error: "Keine Berechtigung." };
  if (!types.some((t) => t.label.trim() && t.key.trim())) {
    return { error: "Mindestens eine Art ist erforderlich." };
  }
  await setAbsenceTypes(types);
  revalidatePath("/admin/settings");
  revalidatePath("/absences");
  return { ok: true };
}

export async function saveNewModulesAction(hrefs: string[]): Promise<{ error?: string; ok?: boolean }> {
  const actorId = await guard("admin.settings");
  if (!actorId) return { error: "Keine Berechtigung." };
  await setNewModules(hrefs);
  revalidatePath("/", "layout");
  return { ok: true };
}
