import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { getAuthUserId } from "@convex-dev/auth/server";
import { nowIso } from "./_utils/time";
import { getCanonicalDocumentByBusinessId, getCanonicalParentDocumentId } from "./_utils/documentRecord";

async function requireUserId(ctx: any): Promise<string> {
  const userId = await getAuthUserId(ctx);
  if (userId === null) {
    throw new Error("未登录");
  }
  return userId;
}

async function requireWorkspaceMember(ctx: any, workspaceId: string, userId: string) {
  const member = await ctx.db
    .query("workspace_members")
    .withIndex("by_workspace_user", (q: any) => q.eq("workspace_id", workspaceId).eq("user_id", userId))
    .first();
  if (!member) {
    throw new Error("无权限");
  }
  return member;
}

type SharePermission = "read" | "edit";

async function resolveSharePermission(ctx: any, doc: any, userId: string): Promise<SharePermission | null> {
  const direct = await ctx.db
    .query("document_shares")
    .withIndex("by_doc_user", (q: any) => q.eq("document_id", doc.id).eq("shared_with_user_id", userId))
    .first();
  if (direct) {
    return direct.permission as SharePermission;
  }

  let parentId: string | null = doc.parent_id ?? null;
  for (let depth = 0; depth < 60 && parentId; depth += 1) {
    const parentShare = await ctx.db
      .query("document_shares")
      .withIndex("by_doc_user", (q: any) => q.eq("document_id", parentId).eq("shared_with_user_id", userId))
      .first();
    if (parentShare && parentShare.include_descendants) {
      return parentShare.permission as SharePermission;
    }

    parentId = await getCanonicalParentDocumentId(ctx, parentId);
  }

  return null;
}

async function resolveGroupSharePermission(ctx: any, doc: any, userId: string): Promise<SharePermission | null> {
  const memberships = await ctx.db
    .query("group_members")
    .withIndex("by_workspace_user", (q: any) => q.eq("workspace_id", doc.workspace_id).eq("user_id", userId))
    .collect();
  if (!memberships.length) return null;
  const groupIds = new Set(memberships.map((m: any) => String(m.group_id)));

  const direct = await ctx.db
    .query("document_group_shares")
    .withIndex("by_document", (q: any) => q.eq("document_id", doc.id))
    .collect();
  for (const row of direct) {
    if (groupIds.has(String(row.group_id))) {
      return "read";
    }
  }

  // 说明：群组共享的“包含子页面”逻辑在 documents.ts 内已实现；这里做一个最小的祖先扫描即可。
  let parentId: string | null = doc.parent_id ?? null;
  for (let depth = 0; depth < 60 && parentId; depth += 1) {
    const parentShares = await ctx.db
      .query("document_group_shares")
      .withIndex("by_document", (q: any) => q.eq("document_id", parentId))
      .collect();
    for (const row of parentShares) {
      if (row.include_descendants && groupIds.has(String(row.group_id))) {
        return "read";
      }
    }
    parentId = await getCanonicalParentDocumentId(ctx, parentId);
  }

  return null;
}

async function requireCanViewDocument(ctx: any, doc: any, userId: string) {
  await requireWorkspaceMember(ctx, doc.workspace_id, userId);
  if (doc.user_id === userId) return;
  if (doc.access_scope === "public") return;
  const perm =
    (await resolveSharePermission(ctx, doc, userId)) ?? (await resolveGroupSharePermission(ctx, doc, userId));
  if (!perm) {
    throw new Error("无权限");
  }
}

async function requireCanModerateDocument(ctx: any, doc: any, userId: string) {
  await requireWorkspaceMember(ctx, doc.workspace_id, userId);
  if (doc.user_id === userId) return;
  if (doc.access_scope === "public") throw new Error("无权限");
  const perm =
    (await resolveSharePermission(ctx, doc, userId)) ?? (await resolveGroupSharePermission(ctx, doc, userId));
  if (perm !== "edit") {
    throw new Error("无权限");
  }
}

const pickUserLite = (user: any) => {
  if (!user) return null;
  return {
    id: String(user._id),
    name: (user.name as string | undefined) ?? null,
    image: (user.image as string | undefined) ?? null,
  };
};

export const listThreadsByDocument = query({
  args: {
    documentId: v.string(),
    includeResolved: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    const userId = await requireUserId(ctx);

    const doc = await getCanonicalDocumentByBusinessId<any>(ctx, args.documentId);
    if (!doc) throw new Error("页面不存在");
    if (doc.deleted_at != null) throw new Error("页面不存在");
    await requireCanViewDocument(ctx, doc, userId);

    const threads = await ctx.db
      .query("comment_threads")
      .withIndex("by_document", (q: any) => q.eq("document_id", args.documentId))
      .collect();

    const includeResolved = Boolean(args.includeResolved);
    const filtered = threads.filter((t: any) => includeResolved || t.resolved_at == null);
    filtered.sort((a: any, b: any) => String(b.last_activity_at).localeCompare(String(a.last_activity_at)));

    const userIds = new Set<string>();
    filtered.forEach((t: any) => {
      if (t.created_by) userIds.add(String(t.created_by));
      if (t.last_comment_by) userIds.add(String(t.last_comment_by));
      if (t.resolved_by) userIds.add(String(t.resolved_by));
    });

    const userById = new Map<string, any>();
    for (const uid of userIds) {
      const u = await ctx.db.get(uid as any);
      if (u) userById.set(uid, u);
    }

    return filtered.map((t: any) => ({
      id: t.id,
      workspaceId: t.workspace_id,
      documentId: t.document_id,
      blockId: t.block_id ?? null,
      resolvedAt: t.resolved_at ?? null,
      resolvedBy: pickUserLite(userById.get(String(t.resolved_by ?? ""))),
      commentCount: Number(t.comment_count ?? 0),
      lastActivityAt: t.last_activity_at,
      lastCommentPreview: t.last_comment_preview ?? null,
      lastCommentBy: pickUserLite(userById.get(String(t.last_comment_by ?? ""))),
      createdAt: t.created_at,
      createdBy: pickUserLite(userById.get(String(t.created_by ?? ""))),
    }));
  },
});

export const listMessagesByThread = query({
  args: { threadId: v.string() },
  handler: async (ctx, args) => {
    const userId = await requireUserId(ctx);

    const thread = await ctx.db
      .query("comment_threads")
      .withIndex("by_thread_id", (q: any) => q.eq("id", args.threadId))
      .first();
    if (!thread) throw new Error("评论线程不存在");

    const doc = await getCanonicalDocumentByBusinessId<any>(ctx, String(thread.document_id));
    if (!doc) throw new Error("页面不存在");
    if (doc.deleted_at != null) throw new Error("页面不存在");
    await requireCanViewDocument(ctx, doc, userId);

    const messages = await ctx.db
      .query("comment_messages")
      .withIndex("by_thread", (q: any) => q.eq("thread_id", args.threadId))
      .collect();
    messages.sort((a: any, b: any) => String(a.created_at).localeCompare(String(b.created_at)));

    const userIds = new Set<string>();
    messages.forEach((m: any) => {
      if (m.created_by) userIds.add(String(m.created_by));
      if (m.deleted_by) userIds.add(String(m.deleted_by));
    });
    const userById = new Map<string, any>();
    for (const uid of userIds) {
      const u = await ctx.db.get(uid as any);
      if (u) userById.set(uid, u);
    }

    return messages.map((m: any) => ({
      id: m.id,
      threadId: m.thread_id,
      documentId: m.document_id,
      blockId: m.block_id ?? null,
      parentId: m.parent_id ?? null,
      body: m.deleted_at ? "" : m.body,
      deletedAt: m.deleted_at ?? null,
      createdAt: m.created_at,
      createdBy: pickUserLite(userById.get(String(m.created_by ?? ""))),
    }));
  },
});

export const createThread = mutation({
  args: {
    id: v.string(),
    documentId: v.string(),
    workspaceId: v.string(),
    blockId: v.optional(v.union(v.string(), v.null())),
    messageId: v.string(),
    body: v.string(),
  },
  handler: async (ctx, args) => {
    const userId = await requireUserId(ctx);

    const doc = await getCanonicalDocumentByBusinessId<any>(ctx, args.documentId);
    if (!doc) throw new Error("页面不存在");
    if (doc.deleted_at != null) throw new Error("页面不存在");
    await requireCanViewDocument(ctx, doc, userId);
    if (String(doc.workspace_id) !== String(args.workspaceId)) {
      throw new Error("workspaceId 不匹配");
    }

    const body = args.body.trim();
    if (!body) throw new Error("评论内容不能为空");
    if (body.length > 4000) throw new Error("评论内容过长（最大 4000 字符）");

    const ts = nowIso();
    await ctx.db.insert("comment_threads", {
      id: args.id,
      workspace_id: args.workspaceId,
      document_id: args.documentId,
      block_id: args.blockId ?? null,
      resolved_at: null,
      resolved_by: null,
      comment_count: 1,
      last_activity_at: ts,
      last_comment_preview: body.slice(0, 160),
      last_comment_by: userId,
      created_by: userId,
      created_at: ts,
    });

    await ctx.db.insert("comment_messages", {
      id: args.messageId,
      thread_id: args.id,
      workspace_id: args.workspaceId,
      document_id: args.documentId,
      block_id: args.blockId ?? null,
      parent_id: null,
      body,
      created_by: userId,
      created_at: ts,
      deleted_at: null,
      deleted_by: null,
    });

    return { ok: true };
  },
});

export const reply = mutation({
  args: {
    messageId: v.string(),
    threadId: v.string(),
    body: v.string(),
  },
  handler: async (ctx, args) => {
    const userId = await requireUserId(ctx);

    const thread = await ctx.db
      .query("comment_threads")
      .withIndex("by_thread_id", (q: any) => q.eq("id", args.threadId))
      .first();
    if (!thread) throw new Error("评论线程不存在");

    const doc = await getCanonicalDocumentByBusinessId<any>(ctx, String(thread.document_id));
    if (!doc) throw new Error("页面不存在");
    if (doc.deleted_at != null) throw new Error("页面不存在");
    await requireCanViewDocument(ctx, doc, userId);

    const body = args.body.trim();
    if (!body) throw new Error("评论内容不能为空");
    if (body.length > 4000) throw new Error("评论内容过长（最大 4000 字符）");

    const ts = nowIso();
    await ctx.db.insert("comment_messages", {
      id: args.messageId,
      thread_id: args.threadId,
      workspace_id: thread.workspace_id,
      document_id: thread.document_id,
      block_id: thread.block_id ?? null,
      parent_id: null,
      body,
      created_by: userId,
      created_at: ts,
      deleted_at: null,
      deleted_by: null,
    });

    await ctx.db.patch(thread._id, {
      comment_count: Number(thread.comment_count ?? 0) + 1,
      last_activity_at: ts,
      last_comment_preview: body.slice(0, 160),
      last_comment_by: userId,
    });

    return { ok: true };
  },
});

export const setResolved = mutation({
  args: { threadId: v.string(), resolved: v.boolean() },
  handler: async (ctx, args) => {
    const userId = await requireUserId(ctx);

    const thread = await ctx.db
      .query("comment_threads")
      .withIndex("by_thread_id", (q: any) => q.eq("id", args.threadId))
      .first();
    if (!thread) throw new Error("评论线程不存在");

    const doc = await getCanonicalDocumentByBusinessId<any>(ctx, String(thread.document_id));
    if (!doc) throw new Error("页面不存在");
    if (doc.deleted_at != null) throw new Error("页面不存在");

    // 说明：解决/取消解决属于“管理”行为，要求可编辑权限，避免只读用户随意改状态。
    await requireCanModerateDocument(ctx, doc, userId);

    const ts = nowIso();
    await ctx.db.patch(thread._id, {
      resolved_at: args.resolved ? ts : null,
      resolved_by: args.resolved ? userId : null,
      last_activity_at: ts,
    });
    return { ok: true };
  },
});

export const editMessage = mutation({
  args: { messageId: v.string(), body: v.string() },
  handler: async (ctx, args) => {
    const userId = await requireUserId(ctx);

    const message = await ctx.db
      .query("comment_messages")
      .withIndex("by_message_id", (q: any) => q.eq("id", args.messageId))
      .first();
    if (!message) throw new Error("评论不存在");
    if (message.deleted_at != null) throw new Error("该评论已删除");
    if (String(message.created_by) !== String(userId)) throw new Error("无权限");

    const thread = await ctx.db
      .query("comment_threads")
      .withIndex("by_thread_id", (q: any) => q.eq("id", message.thread_id))
      .first();
    if (!thread) throw new Error("评论线程不存在");

    const doc = await getCanonicalDocumentByBusinessId<any>(ctx, String(message.document_id));
    if (!doc) throw new Error("页面不存在");
    if (doc.deleted_at != null) throw new Error("页面不存在");
    await requireCanViewDocument(ctx, doc, userId);

    const body = args.body.trim();
    if (!body) throw new Error("评论内容不能为空");
    if (body.length > 4000) throw new Error("评论内容过长（最大 4000 字符）");

    const ts = nowIso();
    await ctx.db.patch(message._id, { body });

    const messages = await ctx.db
      .query("comment_messages")
      .withIndex("by_thread", (q: any) => q.eq("thread_id", message.thread_id))
      .collect();
    messages.sort((a: any, b: any) => String(b.created_at).localeCompare(String(a.created_at)));
    const latestAlive = messages.find((m: any) => m.deleted_at == null) ?? null;

    await ctx.db.patch(thread._id, {
      last_activity_at: ts,
      ...(latestAlive && String(latestAlive.id) === String(message.id)
        ? { last_comment_preview: body.slice(0, 160), last_comment_by: userId }
        : {}),
    });

    return { ok: true };
  },
});

export const deleteMessage = mutation({
  args: { messageId: v.string() },
  handler: async (ctx, args) => {
    const userId = await requireUserId(ctx);

    const message = await ctx.db
      .query("comment_messages")
      .withIndex("by_message_id", (q: any) => q.eq("id", args.messageId))
      .first();
    if (!message) throw new Error("评论不存在");
    if (message.deleted_at != null) return { ok: true };

    const thread = await ctx.db
      .query("comment_threads")
      .withIndex("by_thread_id", (q: any) => q.eq("id", message.thread_id))
      .first();
    if (!thread) throw new Error("评论线程不存在");

    const doc = await getCanonicalDocumentByBusinessId<any>(ctx, String(message.document_id));
    if (!doc) throw new Error("页面不存在");
    if (doc.deleted_at != null) throw new Error("页面不存在");

    if (String(message.created_by) === String(userId)) {
      await requireCanViewDocument(ctx, doc, userId);
    } else {
      await requireCanModerateDocument(ctx, doc, userId);
    }

    const ts = nowIso();
    await ctx.db.patch(message._id, { deleted_at: ts, deleted_by: userId });

    const messages = await ctx.db
      .query("comment_messages")
      .withIndex("by_thread", (q: any) => q.eq("thread_id", message.thread_id))
      .collect();
    messages.sort((a: any, b: any) => String(b.created_at).localeCompare(String(a.created_at)));
    const latestAlive = messages.find((m: any) => m.deleted_at == null) ?? null;

    await ctx.db.patch(thread._id, {
      last_activity_at: ts,
      last_comment_preview: latestAlive ? String(latestAlive.body ?? "").slice(0, 160) : null,
      last_comment_by: latestAlive ? String(latestAlive.created_by ?? "") : null,
    });

    return { ok: true };
  },
});
