import { internalMutation, internalQuery, mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { requireUserId } from "./_utils/auth";
import { nowIso } from "./_utils/time";
import { enqueueIngestMindmapJob } from "./_utils/ingestJobs";
import { internal } from "./_generated/api";
import { requireCanonicalOwnedDocument } from "./_utils/documentRecord";

const defaultMindmapData = {
  data: { text: "中心主题" },
  children: [],
};

function resolveGraceSeconds(): number {
  const raw = process.env.DELETE_GRACE_SECONDS ?? process.env.NEXT_PUBLIC_DELETE_GRACE_SECONDS ?? "600";
  const parsed = Number(raw);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    return 600;
  }
  return Math.floor(parsed);
}

function normalizeMindmapId(docId: string, mindmapId: string): string {
  const raw = String(mindmapId ?? "").trim();
  if (raw) return raw;
  // 兜底：不允许空 mindmapId（否则无法索引）
  return `legacy-${docId}`;
}

async function requireOwnedDocument(ctx: any, userId: string, docId: string) {
  return await requireCanonicalOwnedDocument<any>(ctx, docId, userId);
}

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

    const doc = await requireOwnedDocument(ctx, userId, args.docId);
    const mindmapId = normalizeMindmapId(args.docId, args.mindmapId);

    const row = await ctx.db
      .query("mindmaps")
      .withIndex("by_doc_mindmap", (q) => q.eq("document_id", args.docId).eq("mindmap_id", mindmapId))
      .first();

    // 兼容旧逻辑：不存在或已删除时仍返回默认导图，避免前端卡死。
    if (!row || row.deleted_at != null) {
      return {
        ok: true,
        data: defaultMindmapData,
        meta: {
          workspace_id: doc.workspace_id,
          document_id: args.docId,
          mindmap_id: mindmapId,
          exists: false,
          deleted_at: row?.deleted_at ?? null,
        },
      };
    }

    return {
      ok: true,
      data: row.data ?? defaultMindmapData,
      meta: {
        workspace_id: row.workspace_id,
        document_id: row.document_id,
        mindmap_id: row.mindmap_id,
        exists: true,
        deleted_at: row.deleted_at,
        created_at: row.created_at,
        updated_at: row.updated_at,
      },
    };
  },
});

export const getForIngest = internalQuery({
  args: { userId: v.string(), docId: v.string(), mindmapId: v.string() },
  handler: async (ctx, args) => {
    const doc = await requireOwnedDocument(ctx, args.userId, args.docId);
    const mindmapId = normalizeMindmapId(args.docId, args.mindmapId);

    const row = await ctx.db
      .query("mindmaps")
      .withIndex("by_doc_mindmap", (q) => q.eq("document_id", args.docId).eq("mindmap_id", mindmapId))
      .first();

    if (!row || row.deleted_at != null) {
      return {
        ok: true,
        data: defaultMindmapData,
        meta: {
          workspace_id: doc.workspace_id,
          document_id: args.docId,
          mindmap_id: mindmapId,
          exists: false,
          deleted_at: row?.deleted_at ?? null,
        },
      };
    }

    return {
      ok: true,
      data: row.data ?? defaultMindmapData,
      meta: {
        workspace_id: row.workspace_id,
        document_id: row.document_id,
        mindmap_id: row.mindmap_id,
        exists: true,
        deleted_at: row.deleted_at,
        created_at: row.created_at,
        updated_at: row.updated_at,
      },
    };
  },
});

export const put = mutation({
  args: {
    docId: v.string(),
    mindmapId: v.string(),
    data: v.any(),
    createOnly: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    const userId = await requireUserId(ctx);

    const doc = await requireOwnedDocument(ctx, userId, args.docId);
    const mindmapId = normalizeMindmapId(args.docId, args.mindmapId);

    const existing = await ctx.db
      .query("mindmaps")
      .withIndex("by_doc_mindmap", (q) => q.eq("document_id", args.docId).eq("mindmap_id", mindmapId))
      .first();

    const ts = nowIso();
    const payload = args.data ?? defaultMindmapData;

    if (existing && existing.deleted_at == null && args.createOnly) {
      return {
        ok: true,
        created: false,
        skipped: true,
        workspace_id: doc.workspace_id,
        document_id: args.docId,
        mindmap_id: mindmapId,
        updated_at: existing.updated_at ?? null,
      };
    }

    if (existing) {
      await ctx.db.patch(existing._id, {
        data: payload,
        updated_at: ts,
        deleted_at: null,
        deleted_by: null,
      });
      await enqueueIngestMindmapJob(ctx, { userId, docId: args.docId, mindmapId, debounceMs: 1500 });
      return {
        ok: true,
        created: false,
        skipped: false,
        workspace_id: doc.workspace_id,
        document_id: args.docId,
        mindmap_id: mindmapId,
        updated_at: ts,
      };
    }

    await ctx.db.insert("mindmaps", {
      id: `${args.docId}:${mindmapId}`,
      user_id: userId,
      workspace_id: doc.workspace_id,
      document_id: args.docId,
      mindmap_id: mindmapId,
      data: payload,
      created_at: ts,
      updated_at: ts,
      deleted_at: null,
      deleted_by: null,
    });

    await enqueueIngestMindmapJob(ctx, { userId, docId: args.docId, mindmapId, debounceMs: 1500 });
    return {
      ok: true,
      created: true,
      skipped: false,
      workspace_id: doc.workspace_id,
      document_id: args.docId,
      mindmap_id: mindmapId,
      updated_at: ts,
    };
  },
});

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

    await requireOwnedDocument(ctx, userId, args.docId);
    const mindmapId = normalizeMindmapId(args.docId, args.mindmapId);

    const existing = await ctx.db
      .query("mindmaps")
      .withIndex("by_doc_mindmap", (q) => q.eq("document_id", args.docId).eq("mindmap_id", mindmapId))
      .first();

    if (!existing) {
      // 兼容：不存在也视为成功
      return {
        ok: true,
        moved: 0,
        workspace_id: null,
        document_id: args.docId,
        mindmap_id: mindmapId,
        deleted_at: null,
      };
    }

    if (existing.deleted_at != null) {
      // 已在垃圾桶：保持幂等，避免重复删除导致 updated_at 抖动/重复调度
      return {
        ok: true,
        moved: 0,
        workspace_id: existing.workspace_id,
        document_id: args.docId,
        mindmap_id: mindmapId,
        deleted_at: existing.deleted_at,
      };
    }

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

    // 10 分钟宽限（可恢复）；到期自动清理（彻底删除）。
    // 说明：这里用 scheduler.runAfter 做“按条目调度”，无需全局 cron。
    const graceMs = (resolveGraceSeconds() + 5) * 1000;
    await ctx.scheduler.runAfter(graceMs, internal.mindmaps.purgeIfExpired, {
      docId: args.docId,
      mindmapId,
      deletedAt: ts,
    });
    return {
      ok: true,
      moved: 1,
      workspace_id: existing.workspace_id,
      document_id: args.docId,
      mindmap_id: mindmapId,
      deleted_at: ts,
    };
  },
});

export const purgeIfExpired = internalMutation({
  args: { docId: v.string(), mindmapId: v.string(), deletedAt: v.string() },
  handler: async (ctx, args) => {
    const mindmapId = normalizeMindmapId(args.docId, args.mindmapId);

    const existing = await ctx.db
      .query("mindmaps")
      .withIndex("by_doc_mindmap", (q) => q.eq("document_id", args.docId).eq("mindmap_id", mindmapId))
      .first();

    if (!existing) {
      return { ok: true, deleted: false, reason: "not_found" as const };
    }

    if (existing.deleted_at == null) {
      return { ok: true, deleted: false, reason: "restored" as const };
    }

    if (existing.deleted_at !== args.deletedAt) {
      // 删除时间不一致：说明已被恢复/重新删除过，旧调度作废
      return { ok: true, deleted: false, reason: "changed" as const };
    }

    const graceSeconds = resolveGraceSeconds();
    const deletedTs = Date.parse(existing.deleted_at);
    if (Number.isFinite(deletedTs)) {
      const elapsedMs = Date.now() - deletedTs;
      const graceMs = graceSeconds * 1000;
      if (elapsedMs < graceMs) {
        // 兜底：调度延迟/时钟偏差时，重新排一次剩余时间
        const remainingMs = Math.max(1000, graceMs - elapsedMs + 5000);
        await ctx.scheduler.runAfter(remainingMs, internal.mindmaps.purgeIfExpired, {
          docId: args.docId,
          mindmapId,
          deletedAt: args.deletedAt,
        });
        return { ok: true, deleted: false, rescheduled: true, remainingMs };
      }
    }

    await ctx.db.delete(existing._id);
    return { ok: true, deleted: true };
  },
});

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

    await requireOwnedDocument(ctx, userId, args.docId);
    const mindmapId = normalizeMindmapId(args.docId, args.mindmapId);

    const existing = await ctx.db
      .query("mindmaps")
      .withIndex("by_doc_mindmap", (q) => q.eq("document_id", args.docId).eq("mindmap_id", mindmapId))
      .first();

    if (!existing) {
      throw new Error("未找到可操作的记录");
    }

    const ts = nowIso();
    await ctx.db.patch(existing._id, { deleted_at: null, deleted_by: null, updated_at: ts });
    return {
      ok: true,
      workspace_id: existing.workspace_id,
      document_id: args.docId,
      mindmap_id: mindmapId,
      updated_at: ts,
    };
  },
});

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

    await requireOwnedDocument(ctx, userId, args.docId);
    const mindmapId = normalizeMindmapId(args.docId, args.mindmapId);

    const existing = await ctx.db
      .query("mindmaps")
      .withIndex("by_doc_mindmap", (q) => q.eq("document_id", args.docId).eq("mindmap_id", mindmapId))
      .first();

    if (!existing) {
      throw new Error("未找到可操作的记录");
    }

    await ctx.db.delete(existing._id);
    return {
      ok: true,
      workspace_id: existing.workspace_id,
      document_id: args.docId,
      mindmap_id: mindmapId,
    };
  },
});

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

    // 说明：阶段 6 先按 membership 存在即可，避免引入复杂权限模型。
    const membership = await ctx.db
      .query("workspace_members")
      .withIndex("by_workspace_user", (q) => q.eq("workspace_id", args.workspaceId).eq("user_id", userId))
      .first();
    if (!membership) {
      throw new Error("无权操作该工作空间");
    }

    const rows = await ctx.db
      .query("mindmaps")
      .withIndex("by_workspace", (q) => q.eq("workspace_id", args.workspaceId))
      .collect();

    const includeDeleted = Boolean(args.includeDeleted);

    return rows
      .filter((r) => r.user_id === userId)
      .filter((r) => (includeDeleted ? true : r.deleted_at == null))
      .sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? ""))
      .map((r) => ({
        id: r.id,
        workspace_id: r.workspace_id,
        document_id: r.document_id,
        mindmap_id: r.mindmap_id,
        data: r.data ?? defaultMindmapData,
        created_at: r.created_at,
        updated_at: r.updated_at,
        deleted_at: r.deleted_at,
        deleted_by: r.deleted_by,
      }));
  },
});

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

    const sourceDoc = await requireOwnedDocument(ctx, userId, args.sourceDocId);
    const targetDoc = await requireOwnedDocument(ctx, userId, args.targetDocId);

    // 说明：同一工作空间复制最常见；若跨工作空间复制，这里仍允许，但 mindmaps 会落到目标页面的 workspace_id。
    const sourceRows = await ctx.db
      .query("mindmaps")
      .withIndex("by_workspace", (q) => q.eq("workspace_id", sourceDoc.workspace_id))
      .collect();

    const sourceMindmaps = sourceRows
      .filter((r) => r.user_id === userId)
      .filter((r) => r.document_id === args.sourceDocId)
      .filter((r) => r.deleted_at == null);

    let copied = 0;
    const ts = nowIso();

    for (const r of sourceMindmaps) {
      const existing = await ctx.db
        .query("mindmaps")
        .withIndex("by_doc_mindmap", (q) => q.eq("document_id", args.targetDocId).eq("mindmap_id", r.mindmap_id))
        .first();
      if (existing) continue;

      await ctx.db.insert("mindmaps", {
        id: `${args.targetDocId}:${r.mindmap_id}`,
        user_id: userId,
        workspace_id: targetDoc.workspace_id,
        document_id: args.targetDocId,
        mindmap_id: r.mindmap_id,
        data: r.data ?? defaultMindmapData,
        created_at: ts,
        updated_at: ts,
        deleted_at: null,
        deleted_by: null,
      });
      copied += 1;
    }

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

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

    const membership = await ctx.db
      .query("workspace_members")
      .withIndex("by_workspace_user", (q) => q.eq("workspace_id", args.workspaceId).eq("user_id", userId))
      .first();
    if (!membership) {
      throw new Error("无权操作该工作空间");
    }

    const rows = await ctx.db
      .query("mindmaps")
      .withIndex("by_workspace", (q) => q.eq("workspace_id", args.workspaceId))
      .collect();

    const toDelete = rows.filter((r) => r.user_id === userId && r.deleted_at != null);
    for (const r of toDelete) {
      await ctx.db.delete(r._id);
    }

    return { ok: true, deletedCount: toDelete.length };
  },
});
