import { NextResponse } from "next/server";

import {
  employeeScopeFromAccess,
  requirePermission,
} from "@/lib/authorization";
import { prisma } from "@/lib/prisma";
import { sendAlertMailWithGraph } from "@/services/alertes-mail-graph.service";
import { getEmployeByCos } from "@/services/employes.service";
import {
  buildMailHtml,
  type GraphAttachment,
  loadSenderSignature,
  loadSignatureLogo,
} from "@/services/mail-rendering.service";
import { generateProcedurePdf } from "@/services/procedures-pdf.service";
import { getProcedureById } from "@/services/procedures.service";
import { employeeEmailSchema } from "@/validators/employee-communications";

type RouteContext = { params: Promise<{ cos: string }> };

function parseCos(value: string) {
  const cos = Number(value);
  return Number.isInteger(cos) && cos > 0 ? cos : null;
}

function normalizeEmail(value: string | null | undefined) {
  return value?.trim().toLowerCase() || null;
}

function uniqueEmails(values: Array<string | null>) {
  return Array.from(new Set(values.filter((value): value is string => Boolean(value))));
}

function attachmentName(name: string, procedureId: number) {
  const safeName = name
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-zA-Z0-9-]+/g, "-")
    .replace(/^-+|-+$/g, "");
  return `demande-procedure-${safeName || "salarie"}-${procedureId}.pdf`;
}

async function visibleEmployee(
  cos: number,
  access: Parameters<typeof employeeScopeFromAccess>[0],
) {
  return getEmployeByCos(cos, employeeScopeFromAccess(access));
}

async function employeeContact(cos: number) {
  return prisma.employes.findUnique({
    where: { COS: cos },
    select: {
      COS: true,
      TIT: true,
      NSA: true,
      PRE: true,
      EmailPerso: true,
      EmailE2e: true,
    },
  });
}

export async function GET(request: Request, { params }: RouteContext) {
  const auth = await requirePermission(request, "autres", "write");
  if (!auth.ok) return auth.response;

  const cos = parseCos((await params).cos);
  if (cos === null) {
    return NextResponse.json({ message: "COS invalide" }, { status: 400 });
  }
  if (!(await visibleEmployee(cos, auth.access))) {
    return NextResponse.json({ message: "Salarié introuvable" }, { status: 404 });
  }

  const employee = await employeeContact(cos);
  if (!employee) {
    return NextResponse.json({ message: "Salarié introuvable" }, { status: 404 });
  }

  const [items, procedures, signature] = await Promise.all([
    prisma.employee_communications.findMany({
      where: { employee_cos: cos, channel: "EMAIL" },
      orderBy: { created_at: "desc" },
      take: 100,
      select: {
        id: true,
        recipient: true,
        subject: true,
        procedure_request_id: true,
        attachment_name: true,
        status: true,
        created_at: true,
        sent_at: true,
        sent_by: { select: { username: true, email: true } },
      },
    }),
    prisma.procedure_requests.findMany({
      where: { employee_cos: cos },
      orderBy: { created_at: "desc" },
      select: { id: true, request_type: true, created_at: true },
    }),
    loadSenderSignature(auth.principal.id),
  ]);

  const personal = normalizeEmail(employee.EmailPerso);
  const professional = normalizeEmail(employee.EmailE2e);
  return NextResponse.json({
    employee: {
      name: [employee.TIT, employee.NSA, employee.PRE].filter(Boolean).join(" "),
      emails: uniqueEmails([professional, personal]).map((address) => ({
        address,
        kind: address === professional ? "professionnelle" : "personnelle",
      })),
    },
    items,
    procedures,
    signature: signature
      ? {
          enabled: true,
          displayName: signature.displayName,
          jobTitle: signature.jobTitle,
        }
      : { enabled: false, displayName: "", jobTitle: "" },
  });
}

export async function POST(request: Request, { params }: RouteContext) {
  const auth = await requirePermission(request, "autres", "write");
  if (!auth.ok) return auth.response;

  const cos = parseCos((await params).cos);
  if (cos === null) {
    return NextResponse.json({ message: "COS invalide" }, { status: 400 });
  }
  if (!(await visibleEmployee(cos, auth.access))) {
    return NextResponse.json({ message: "Salarié introuvable" }, { status: 404 });
  }

  const parsed = employeeEmailSchema.safeParse(
    await request.json().catch(() => null),
  );
  if (!parsed.success) {
    return NextResponse.json(
      { message: "E-mail invalide", issues: parsed.error.issues },
      { status: 400 },
    );
  }

  const employee = await employeeContact(cos);
  if (!employee) {
    return NextResponse.json({ message: "Salarié introuvable" }, { status: 404 });
  }

  const allowedEmails = uniqueEmails([
    normalizeEmail(employee.EmailE2e),
    normalizeEmail(employee.EmailPerso),
  ]);
  const recipient = normalizeEmail(parsed.data.recipient)!;
  if (!allowedEmails.includes(recipient)) {
    return NextResponse.json(
      { message: "Cette adresse ne correspond pas au salarié sélectionné." },
      { status: 400 },
    );
  }

  let procedure: Awaited<ReturnType<typeof getProcedureById>> = null;
  let pdf: Buffer | null = null;
  let pdfName: string | null = null;
  if (parsed.data.procedureId) {
    procedure = await getProcedureById(parsed.data.procedureId);
    if (!procedure || procedure.employee_cos !== cos) {
      return NextResponse.json(
        { message: "La demande de procédure sélectionnée est invalide." },
        { status: 400 },
      );
    }
    pdf = await generateProcedurePdf(procedure);
    pdfName = attachmentName(
      [employee.NSA, employee.PRE].filter(Boolean).join("-"),
      procedure.id,
    );
  }

  const communication = await prisma.employee_communications.create({
    data: {
      employee_cos: cos,
      channel: "EMAIL",
      recipient,
      subject: parsed.data.subject,
      body_text: parsed.data.bodyText,
      procedure_request_id: procedure?.id ?? null,
      attachment_name: pdfName,
      status: "PROCESSING",
      sent_by_user_id: auth.principal.id,
    },
  });

  try {
    const signature = await loadSenderSignature(auth.principal.id);
    const logoAttachment = await loadSignatureLogo(signature);
    const attachments: GraphAttachment[] = [];
    if (pdf && pdfName) {
      attachments.push({
        name: pdfName,
        contentType: "application/pdf",
        contentBytes: pdf.toString("base64"),
      });
    }
    if (logoAttachment) attachments.push(logoAttachment);

    const result = await sendAlertMailWithGraph({
      recipients: [recipient],
      subject: parsed.data.subject,
      html: buildMailHtml(
        parsed.data.bodyText,
        signature,
        Boolean(logoAttachment),
      ),
      attachments: attachments.length ? attachments : undefined,
    });

    const item = await prisma.employee_communications.update({
      where: { id: communication.id },
      data: {
        status: "SENT",
        provider_message_id: result.messageId,
        error_message: null,
        sent_at: new Date(),
      },
      select: { id: true, status: true, sent_at: true },
    });
    return NextResponse.json(
      { message: "L’e-mail a été envoyé.", item },
      { status: 201 },
    );
  } catch (error) {
    console.error("[employee-email] Échec de l’envoi Graph", error);
    await prisma.employee_communications.update({
      where: { id: communication.id },
      data: {
        status: "FAILED",
        error_message: String(error).slice(0, 2_000),
      },
    });
    return NextResponse.json(
      { message: "L’e-mail n’a pas pu être envoyé. Vérifiez la configuration Graph." },
      { status: 502 },
    );
  }
}
