import "server-only";
import { prisma } from "@/lib/prisma";
import type { ChannelType, AttachmentKind } from "@prisma/client";

export type ChannelListItem = {
  id: string;
  type: ChannelType;
  title: string;
  avatarUrl: string | null;
  otherUserId: string | null;
  lastMessage: { preview: string; at: string; author: string } | null;
  unread: number;
};

function channelTitle(
  type: ChannelType,
  name: string | null,
  projectName: string | null | undefined,
  teamName: string | null | undefined,
  otherMemberName: string | null,
): string {
  if (name) return name;
  if (type === "DIRECT") return otherMemberName ?? "Direktnachricht";
  if (type === "PROJECT") return projectName ?? "Projektkanal";
  if (type === "TEAM") return teamName ?? "Teamkanal";
  return "Kanal";
}

function previewFor(body: string, attachmentKind?: AttachmentKind): string {
  if (body.trim()) return body.slice(0, 80);
  if (attachmentKind === "IMAGE") return "📷 Bild";
  if (attachmentKind === "AUDIO") return "🎤 Sprachnachricht";
  if (attachmentKind === "FILE") return "📎 Datei";
  return "";
}

/** Alle Kanäle eines Nutzers inkl. Vorschau, Avatar & ungelesener Anzahl. */
export async function getUserChannels(userId: string): Promise<ChannelListItem[]> {
  const memberships = await prisma.channelMember.findMany({
    where: { userId },
    select: {
      lastReadAt: true,
      channel: {
        select: {
          id: true,
          type: true,
          name: true,
          avatarUrl: true,
          project: { select: { name: true } },
          team: { select: { name: true } },
          members: {
            select: {
              user: {
                select: { id: true, name: true, displayName: true, avatarUrl: true },
              },
            },
          },
          messages: {
            take: 1,
            orderBy: { createdAt: "desc" },
            where: { deletedAt: null },
            select: {
              body: true,
              createdAt: true,
              author: { select: { name: true, displayName: true } },
              attachments: { take: 1, select: { kind: true } },
            },
          },
        },
      },
    },
  });

  const items = await Promise.all(
    memberships.map(async (m) => {
      const c = m.channel;
      const other = c.members.map((mm) => mm.user).find((u) => u.id !== userId);
      const last = c.messages[0];

      const unread = await prisma.message.count({
        where: {
          channelId: c.id,
          deletedAt: null,
          authorId: { not: userId },
          ...(m.lastReadAt ? { createdAt: { gt: m.lastReadAt } } : {}),
        },
      });

      const avatarUrl =
        c.type === "DIRECT" ? (other?.avatarUrl ?? null) : (c.avatarUrl ?? null);

      return {
        id: c.id,
        type: c.type,
        title: channelTitle(
          c.type,
          c.name,
          c.project?.name,
          c.team?.name,
          other ? other.displayName ?? other.name : null,
        ),
        avatarUrl,
        otherUserId: c.type === "DIRECT" ? (other?.id ?? null) : null,
        lastMessage: last
          ? {
              preview: previewFor(last.body, last.attachments[0]?.kind),
              at: last.createdAt.toISOString(),
              author: last.author.displayName ?? last.author.name,
            }
          : null,
        unread,
      } satisfies ChannelListItem;
    }),
  );

  return items.sort((a, b) => {
    const at = a.lastMessage?.at ?? "";
    const bt = b.lastMessage?.at ?? "";
    return bt.localeCompare(at);
  });
}

export type ChannelMemberDTO = { id: string; name: string; avatarUrl: string | null };

/** Liefert den Kanal nur, wenn der Nutzer Mitglied ist — sonst null. */
export async function getChannelWithAccess(channelId: string, userId: string) {
  const member = await prisma.channelMember.findUnique({
    where: { channelId_userId: { channelId, userId } },
    select: { lastReadAt: true },
  });
  if (!member) return null;

  const channel = await prisma.channel.findUnique({
    where: { id: channelId },
    select: {
      id: true,
      type: true,
      name: true,
      avatarUrl: true,
      createdById: true,
      project: { select: { name: true } },
      team: { select: { name: true } },
      members: {
        select: {
          user: {
            select: { id: true, name: true, displayName: true, avatarUrl: true },
          },
        },
      },
    },
  });
  if (!channel) return null;

  const other = channel.members.map((m) => m.user).find((u) => u.id !== userId);
  const avatarUrl =
    channel.type === "DIRECT" ? (other?.avatarUrl ?? null) : (channel.avatarUrl ?? null);

  return {
    id: channel.id,
    type: channel.type,
    title: channelTitle(
      channel.type,
      channel.name,
      channel.project?.name,
      channel.team?.name,
      other ? other.displayName ?? other.name : null,
    ),
    avatarUrl,
    canEditAvatar: channel.type !== "DIRECT",
    createdById: channel.createdById,
    otherUserId: channel.type === "DIRECT" ? (other?.id ?? null) : null,
    memberCount: channel.members.length,
    members: channel.members.map((m) => ({
      id: m.user.id,
      name: m.user.displayName ?? m.user.name,
      avatarUrl: m.user.avatarUrl ?? null,
    })) satisfies ChannelMemberDTO[],
  };
}

export type ChatAttachment = {
  id: string;
  kind: AttachmentKind;
  mime: string;
  name: string;
  url: string;
  width: number | null;
  height: number | null;
  durationMs: number | null;
};

export type MessageReactionDTO = { emoji: string; count: number; mine: boolean };

export type ChatMessage = {
  id: string;
  body: string;
  authorId: string;
  authorName: string;
  authorAvatar: string | null;
  createdAt: string;
  editedAt: string | null;
  deleted: boolean;
  pinned: boolean;
  replyTo: { id: string; author: string; preview: string } | null;
  reactions: MessageReactionDTO[];
  attachments: ChatAttachment[];
};

/** Nachrichten eines Kanals (mit Zugriffsprüfung). */
export async function getMessages(
  channelId: string,
  userId: string,
  take = 60,
): Promise<ChatMessage[] | null> {
  const member = await prisma.channelMember.findUnique({
    where: { channelId_userId: { channelId, userId } },
    select: { channelId: true },
  });
  if (!member) return null;

  const rows = await prisma.message.findMany({
    where: { channelId },
    orderBy: { createdAt: "desc" },
    take,
    select: {
      id: true,
      body: true,
      authorId: true,
      createdAt: true,
      editedAt: true,
      deletedAt: true,
      pinnedAt: true,
      author: { select: { name: true, displayName: true, avatarUrl: true } },
      replyTo: {
        select: {
          id: true,
          body: true,
          deletedAt: true,
          author: { select: { name: true, displayName: true } },
          attachments: { take: 1, select: { kind: true } },
        },
      },
      reactions: { select: { emoji: true, userId: true } },
      attachments: {
        select: {
          id: true,
          kind: true,
          mime: true,
          originalName: true,
          width: true,
          height: true,
          durationMs: true,
        },
      },
    },
  });

  return rows
    .map((r) => {
      const deleted = Boolean(r.deletedAt);

      // Reaktionen je Emoji aggregieren.
      const byEmoji = new Map<string, { count: number; mine: boolean }>();
      for (const re of r.reactions) {
        const cur = byEmoji.get(re.emoji) ?? { count: 0, mine: false };
        cur.count += 1;
        if (re.userId === userId) cur.mine = true;
        byEmoji.set(re.emoji, cur);
      }

      return {
        id: r.id,
        body: deleted ? "" : r.body,
        authorId: r.authorId,
        authorName: r.author.displayName ?? r.author.name,
        authorAvatar: r.author.avatarUrl ?? null,
        createdAt: r.createdAt.toISOString(),
        editedAt: r.editedAt ? r.editedAt.toISOString() : null,
        deleted,
        pinned: Boolean(r.pinnedAt),
        replyTo: r.replyTo
          ? {
              id: r.replyTo.id,
              author: r.replyTo.author.displayName ?? r.replyTo.author.name,
              preview: r.replyTo.deletedAt
                ? "Nachricht gelöscht"
                : previewFor(r.replyTo.body, r.replyTo.attachments[0]?.kind),
            }
          : null,
        reactions: deleted
          ? []
          : [...byEmoji.entries()].map(([emoji, v]) => ({ emoji, count: v.count, mine: v.mine })),
        attachments: deleted
          ? []
          : r.attachments.map((a) => ({
              id: a.id,
              kind: a.kind,
              mime: a.mime,
              name: a.originalName,
              url: `/api/chat/attachment/${a.id}`,
              width: a.width,
              height: a.height,
              durationMs: a.durationMs,
            })),
      } satisfies ChatMessage;
    })
    .reverse();
}

/**
 * Lesestand der übrigen Kanalmitglieder — Grundlage für die Anzeige
 * „gesendet / angekommen / gelesen" an eigenen Nachrichten.
 * `lastSeenAt` stammt vom Präsenz-Signal: war jemand nach dem Absenden
 * online, gilt die Nachricht als zugestellt.
 */
export type ReadReceipt = { userId: string; lastReadAt: string | null; lastSeenAt: string | null };

export async function getReadReceipts(
  channelId: string,
  userId: string,
): Promise<ReadReceipt[]> {
  const rows = await prisma.channelMember.findMany({
    where: { channelId, userId: { not: userId } },
    select: {
      userId: true,
      lastReadAt: true,
      user: { select: { lastSeenAt: true } },
    },
  });

  return rows.map((r) => ({
    userId: r.userId,
    lastReadAt: r.lastReadAt ? r.lastReadAt.toISOString() : null,
    lastSeenAt: r.user.lastSeenAt ? r.user.lastSeenAt.toISOString() : null,
  }));
}

export type PinnedMessageDTO = {
  id: string;
  author: string;
  preview: string;
  image: string | null;
  createdAt: string;
};

/** Fixierte Nachrichten eines Kanals (mit Zugriffsprüfung). */
export async function getPinnedMessages(
  channelId: string,
  userId: string,
): Promise<PinnedMessageDTO[] | null> {
  const member = await prisma.channelMember.findUnique({
    where: { channelId_userId: { channelId, userId } },
    select: { channelId: true },
  });
  if (!member) return null;

  const rows = await prisma.message.findMany({
    where: { channelId, pinnedAt: { not: null }, deletedAt: null },
    orderBy: { pinnedAt: "desc" },
    select: {
      id: true,
      body: true,
      createdAt: true,
      author: { select: { name: true, displayName: true } },
      attachments: { select: { id: true, kind: true } },
    },
  });

  return rows.map((r) => {
    const image = r.attachments.find((a) => a.kind === "IMAGE");
    return {
      id: r.id,
      author: r.author.displayName ?? r.author.name,
      preview: previewFor(r.body, r.attachments[0]?.kind),
      image: image ? `/api/chat/attachment/${image.id}` : null,
      createdAt: r.createdAt.toISOString(),
    };
  });
}
