import { and, desc, eq, inArray, sql } from "drizzle-orm";

import { getDatabase } from "@/modules/core/server/db";
import { accounts, noticeReadReceipts, notices } from "@/modules/core/server/schema";
import type { Notice, NoticeCenterData, NoticeInput } from "@/modules/notices/types";

export type NoticeMutationFailure = {
  success: false;
  reason: string;
  status: number;
};

type ReadCountRow = {
  noticeId: string;
  count: number;
};

export function isNoticeMutationFailure(value: unknown): value is NoticeMutationFailure {
  return (
    typeof value === "object" &&
    value !== null &&
    "success" in value &&
    (value as { success?: unknown }).success === false
  );
}

function iso(value: Date): string {
  return value.toISOString();
}

function optionalIso(value: Date | null): string | undefined {
  return value ? iso(value) : undefined;
}

function toNotice(
  row: typeof notices.$inferSelect,
  accountNameById: Map<string, string>,
  readCountByNoticeId: Map<string, number>,
  readNoticeIds: Set<string>,
): Notice {
  const createdByAccountId = row.createdByAccountId ?? undefined;
  const updatedByAccountId = row.updatedByAccountId ?? undefined;
  return {
    id: row.id,
    organizationId: row.organizationId,
    title: row.title,
    content: row.content,
    audience: row.audience,
    status: row.status,
    publishedAt: optionalIso(row.publishedAt),
    createdByAccountId,
    createdByName: createdByAccountId ? accountNameById.get(createdByAccountId) : undefined,
    updatedByAccountId,
    updatedByName: updatedByAccountId ? accountNameById.get(updatedByAccountId) : undefined,
    readCount: readCountByNoticeId.get(row.id) ?? 0,
    hasRead: readNoticeIds.has(row.id),
    createdAt: iso(row.createdAt),
    updatedAt: iso(row.updatedAt),
  };
}

export async function listNoticeCenterData(organizationId: string, accountId: string): Promise<NoticeCenterData> {
  const database = getDatabase();
  const noticeRows = await database.select().from(notices).where(eq(notices.organizationId, organizationId)).orderBy(desc(notices.updatedAt));
  const noticeIds = noticeRows.map((notice) => notice.id);
  const accountIds = Array.from(
    new Set(
      noticeRows
        .flatMap((notice) => [notice.createdByAccountId, notice.updatedByAccountId])
        .filter((item): item is string => typeof item === "string"),
    ),
  );
  const [accountRows, readCountRows, readRows] = await Promise.all([
    accountIds.length > 0 ? database.select({ id: accounts.id, name: accounts.name }).from(accounts).where(inArray(accounts.id, accountIds)) : [],
    noticeIds.length > 0
      ? database
          .select({ noticeId: noticeReadReceipts.noticeId, count: sql<number>`count(*)::int` })
          .from(noticeReadReceipts)
          .where(and(eq(noticeReadReceipts.organizationId, organizationId), inArray(noticeReadReceipts.noticeId, noticeIds)))
          .groupBy(noticeReadReceipts.noticeId)
      : ([] as ReadCountRow[]),
    noticeIds.length > 0
      ? database
          .select({ noticeId: noticeReadReceipts.noticeId })
          .from(noticeReadReceipts)
          .where(
            and(
              eq(noticeReadReceipts.organizationId, organizationId),
              eq(noticeReadReceipts.accountId, accountId),
              inArray(noticeReadReceipts.noticeId, noticeIds),
            ),
          )
      : [],
  ]);
  const readCountByNoticeId = new Map(readCountRows.map((row) => [row.noticeId, Number(row.count)]));
  const readNoticeIds = new Set(readRows.map((row) => row.noticeId));
  const noticeData = noticeRows.map((notice) =>
    toNotice(
      notice,
      new Map(accountRows.map((account) => [account.id, account.name])),
      readCountByNoticeId,
      readNoticeIds,
    ),
  );

  return {
    metrics: {
      published: noticeRows.filter((notice) => notice.status === "published").length,
      drafts: noticeRows.filter((notice) => notice.status === "draft").length,
      unread: noticeRows.filter((notice) => notice.status === "published" && !readNoticeIds.has(notice.id)).length,
      retracted: noticeRows.filter((notice) => notice.status === "retracted").length,
    },
    notices: noticeData,
  };
}

export async function createNotice(input: NoticeInput & { accountId: string; organizationId: string }): Promise<Notice | NoticeMutationFailure> {
  const database = getDatabase();
  const now = new Date();
  const rows = await database
    .insert(notices)
    .values({
      organizationId: input.organizationId,
      title: input.title,
      content: input.content,
      audience: input.audience,
      status: input.status,
      publishedAt: input.status === "published" ? now : undefined,
      createdByAccountId: input.accountId,
      updatedByAccountId: input.accountId,
    })
    .returning();
  const notice = rows[0];
  return notice ? toNotice(notice, new Map(), new Map(), new Set()) : { success: false, reason: "公告创建失败", status: 500 };
}

export async function updateNotice(input: NoticeInput & { accountId: string; id: string; organizationId: string }): Promise<Notice | NoticeMutationFailure> {
  const database = getDatabase();
  const now = new Date();
  const rows = await database
    .update(notices)
    .set({
      title: input.title,
      content: input.content,
      audience: input.audience,
      status: input.status,
      publishedAt: input.status === "published" ? now : null,
      updatedByAccountId: input.accountId,
      updatedAt: now,
    })
    .where(and(eq(notices.id, input.id), eq(notices.organizationId, input.organizationId)))
    .returning();
  const notice = rows[0];
  return notice ? toNotice(notice, new Map(), new Map(), new Set()) : { success: false, reason: "公告不存在", status: 404 };
}

export async function deleteNotice(input: { id: string; organizationId: string }): Promise<Notice | NoticeMutationFailure> {
  const database = getDatabase();
  const rows = await database.delete(notices).where(and(eq(notices.id, input.id), eq(notices.organizationId, input.organizationId))).returning();
  const notice = rows[0];
  return notice ? toNotice(notice, new Map(), new Map(), new Set()) : { success: false, reason: "公告不存在", status: 404 };
}

export async function markNoticeRead(input: { accountId: string; id: string; organizationId: string }): Promise<Notice | NoticeMutationFailure> {
  const database = getDatabase();
  const rows = await database.select().from(notices).where(and(eq(notices.id, input.id), eq(notices.organizationId, input.organizationId)));
  const notice = rows[0];
  if (!notice) {
    return { success: false, reason: "公告不存在", status: 404 };
  }

  await database
    .insert(noticeReadReceipts)
    .values({ organizationId: input.organizationId, noticeId: input.id, accountId: input.accountId })
    .onConflictDoUpdate({
      target: [noticeReadReceipts.noticeId, noticeReadReceipts.accountId],
      set: { readAt: new Date() },
    });
  return toNotice(notice, new Map(), new Map([[notice.id, 1]]), new Set([notice.id]));
}
