import { NextResponse } from "next/server";
import { DepartmentKey } from "@prisma/client";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { parseStringArray } from "@/lib/marketing/meetings";
import { canManageProjects, mapProjectMeeting } from "@/lib/projects/persistence";

type ParticipantInput = {
  userId?: string;
  displayName?: string;
};

type UpdateMeetingBody = {
  action?: "schedule" | "complete" | "cancel";
  title?: string;
  agenda?: string;
  scheduledFor?: string;
  startAt?: string;
  endAt?: string;
  commitments?: Array<{
    title?: string;
    description?: string;
    ownerName?: string;
    dueDate?: string;
  }>;
  participantNames?: unknown;
  participants?: unknown;
};

function parseIsoDate(value: string | undefined): Date | null {
  if (!value) {
    return null;
  }

  const parsed = new Date(value);
  if (Number.isNaN(parsed.getTime())) {
    return null;
  }

  return parsed;
}

function parseParticipantInput(value: unknown): ParticipantInput[] {
  if (!Array.isArray(value)) {
    return [];
  }

  const parsed: ParticipantInput[] = [];

  for (const entry of value) {
    if (!entry || typeof entry !== "object") {
      continue;
    }

    const payload = entry as { userId?: unknown; displayName?: unknown };
    const userId = typeof payload.userId === "string" ? payload.userId.trim() : "";
    const displayName = typeof payload.displayName === "string" ? payload.displayName.trim() : "";

    if (!userId && !displayName) {
      continue;
    }

    const participant: ParticipantInput = {};
    if (userId) {
      participant.userId = userId;
    }

    if (displayName) {
      participant.displayName = displayName;
    }

    parsed.push(participant);
  }

  return parsed;
}

async function normalizeParticipants(
  inputParticipants: ParticipantInput[],
  participantNamesInput: string[]
): Promise<Array<{ userId: string | null; displayName: string }>> {
  const userIds = Array.from(new Set(inputParticipants.map((entry) => entry.userId).filter((value): value is string => Boolean(value))));
  const users =
    userIds.length > 0
      ? await prisma.user.findMany({
          where: {
            id: { in: userIds },
            status: "active",
          },
          select: {
            id: true,
            name: true,
            email: true,
          },
        })
      : [];

  const userMap = new Map(users.map((user) => [user.id, user]));
  const dedupe = new Set<string>();
  const participants: Array<{ userId: string | null; displayName: string }> = [];

  for (const entry of inputParticipants) {
    const normalizedUserId = entry.userId?.trim();
    const linkedUser = normalizedUserId ? userMap.get(normalizedUserId) : null;
    const displayName = linkedUser ? linkedUser.name?.trim() || linkedUser.email : entry.displayName?.trim() || "";

    if (!displayName) {
      continue;
    }

    const key = linkedUser ? `user:${linkedUser.id}` : `name:${displayName.toLowerCase()}`;
    if (dedupe.has(key)) {
      continue;
    }

    dedupe.add(key);
    participants.push({
      userId: linkedUser?.id ?? null,
      displayName,
    });
  }

  for (const displayName of participantNamesInput) {
    const cleanName = displayName.trim();
    if (!cleanName) {
      continue;
    }

    const key = `name:${cleanName.toLowerCase()}`;
    if (dedupe.has(key)) {
      continue;
    }

    dedupe.add(key);
    participants.push({
      userId: null,
      displayName: cleanName,
    });
  }

  return participants;
}

export async function PATCH(request: Request, context: { params: { id: string } }) {
  const session = await getServerSession(authOptions);
  if (!session?.user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  if (!canManageProjects(session.user.role, session.user.department)) {
    return NextResponse.json({ error: "Only owners or leaders can update meetings." }, { status: 403 });
  }

  const meetingId = context.params.id;
  const existing = await prisma.marketingMeeting.findFirst({
    where: {
      id: meetingId,
      department: DepartmentKey.proyectos,
    },
    include: {
      participants: {
        orderBy: [{ responseStatus: "asc" }, { displayName: "asc" }],
      },
      calendarEvent: {
        select: { id: true },
      },
    },
  });

  if (!existing) {
    return NextResponse.json({ error: "Meeting not found." }, { status: 404 });
  }

  const body = (await request.json()) as UpdateMeetingBody;
  const action = body.action;

  if (!action || (action !== "schedule" && action !== "complete" && action !== "cancel")) {
    return NextResponse.json({ error: "Invalid action." }, { status: 400 });
  }

  if (action === "cancel") {
    const meeting = await prisma.marketingMeeting.update({
      where: { id: meetingId },
      data: {
        status: "cancelled",
      },
      include: {
        commitments: {
          orderBy: { createdAt: "asc" },
        },
        participants: {
          orderBy: [{ responseStatus: "asc" }, { displayName: "asc" }],
        },
        calendarEvent: {
          select: { id: true },
        },
      },
    });

    return NextResponse.json({ ok: true, meeting: mapProjectMeeting(meeting) });
  }

  if (action === "complete") {
    const commitmentsInput = (body.commitments ?? [])
      .map((item) => ({
        title: item.title?.trim() ?? "",
        description: item.description?.trim() || null,
        ownerName: item.ownerName?.trim() || null,
        dueDate: parseIsoDate(item.dueDate),
      }))
      .filter((item) => item.title.length > 0);

    const meeting = await prisma.$transaction(async (tx) => {
      if (commitmentsInput.length > 0) {
        await tx.marketingCommitment.createMany({
          data: commitmentsInput.map((item) => ({
            meetingId,
            title: item.title,
            description: item.description,
            ownerName: item.ownerName,
            dueDate: item.dueDate,
          })),
        });
      }

      return tx.marketingMeeting.update({
        where: { id: meetingId },
        data: {
          status: "completed",
          completedAt: new Date(),
        },
        include: {
          commitments: {
            orderBy: { createdAt: "asc" },
          },
          participants: {
            orderBy: [{ responseStatus: "asc" }, { displayName: "asc" }],
          },
          calendarEvent: {
            select: { id: true },
          },
        },
      });
    });

    return NextResponse.json({ ok: true, meeting: mapProjectMeeting(meeting) });
  }

  const startAt = parseIsoDate(body.startAt ?? body.scheduledFor);
  let endAt = parseIsoDate(body.endAt);

  if (!startAt) {
    return NextResponse.json({ error: "startAt/scheduledFor is required." }, { status: 400 });
  }

  if (!endAt) {
    endAt = new Date(startAt.getTime() + 60 * 60 * 1000);
  }

  if (endAt.getTime() <= startAt.getTime()) {
    return NextResponse.json({ error: "endAt must be later than startAt." }, { status: 400 });
  }

  const title = body.title?.trim();
  const agenda = body.agenda?.trim();
  const participantInput = parseParticipantInput(body.participants);
  const participantNames = parseStringArray(body.participantNames);
  const shouldReplaceParticipants = body.participants !== undefined || body.participantNames !== undefined;

  const meeting = await prisma.$transaction(async (tx) => {
    let nextParticipantNames: string[] | undefined;

    if (shouldReplaceParticipants) {
      const normalizedParticipants = await normalizeParticipants(participantInput, participantNames);
      if (normalizedParticipants.length === 0) {
        throw new Error("At least one participant is required.");
      }

      const existingByKey = new Map(
        existing.participants.map((participant) => [participant.userId ? `user:${participant.userId}` : `name:${participant.displayName.toLowerCase()}`, participant])
      );

      await tx.marketingMeetingParticipant.deleteMany({
        where: { meetingId },
      });

      await tx.marketingMeetingParticipant.createMany({
        data: normalizedParticipants.map((participant) => {
          const key = participant.userId ? `user:${participant.userId}` : `name:${participant.displayName.toLowerCase()}`;
          const previous = existingByKey.get(key);
          const isCurrentUser = participant.userId === session.user.id;

          return {
            meetingId,
            userId: participant.userId,
            displayName: participant.displayName,
            responseStatus: previous?.responseStatus ?? (isCurrentUser ? "accepted" : "pending"),
            respondedAt: previous?.respondedAt ?? (isCurrentUser ? new Date() : null),
          };
        }),
      });

      nextParticipantNames = normalizedParticipants.map((participant) => participant.displayName);
    }

    await tx.marketingMeeting.update({
      where: { id: meetingId },
      data: {
        status: "scheduled",
        title: title && title.length > 0 ? title : undefined,
        agenda: agenda && agenda.length > 0 ? agenda : undefined,
        scheduledFor: startAt,
        participantNames: nextParticipantNames,
      },
    });

    await tx.projectCalendarEvent.upsert({
      where: {
        meetingId,
      },
      update: {
        title: title && title.length > 0 ? title : existing.title,
        notes: agenda && agenda.length > 0 ? agenda : existing.agenda,
        startAt,
        endAt,
        visibility: "team",
      },
      create: {
        department: DepartmentKey.proyectos,
        ownerUserId: session.user.id,
        meetingId,
        title: title && title.length > 0 ? title : existing.title,
        notes: agenda && agenda.length > 0 ? agenda : existing.agenda,
        startAt,
        endAt,
        visibility: "team",
      },
    });

    return tx.marketingMeeting.findUniqueOrThrow({
      where: { id: meetingId },
      include: {
        commitments: {
          orderBy: { createdAt: "asc" },
        },
        participants: {
          orderBy: [{ responseStatus: "asc" }, { displayName: "asc" }],
        },
        calendarEvent: {
          select: { id: true },
        },
      },
    });
  });

  return NextResponse.json({ ok: true, meeting: mapProjectMeeting(meeting) });
}
