import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
import { assertPermission, actorLite, AppError, requireActor, currentActor } from "./lib/access";
import { audit } from "./lib/audit";
import { getSettings, saveSettings } from "./lib/settings";
import { NOTIFICATION_EVENTS } from "../src/lib/notification-events";

/** Public-safe subset for the login shell and app chrome. */
export const publicInfo = query({
  args: {},
  returns: v.object({ businessName: v.string(), currency: v.string(), timezone: v.string() }),
  handler: async (ctx) => {
    const s = await getSettings(ctx);
    return { businessName: s.businessName, currency: s.currency, timezone: s.timezone };
  },
});

export const get = query({
  args: {},
  returns: v.any(),
  handler: async (ctx) => {
    await requireActor(ctx);
    return getSettings(ctx);
  },
});

export const update = mutation({
  args: { patch: v.any(), group: v.optional(v.string()) },
  returns: v.null(),
  handler: async (ctx, { patch, group }) => {
    const user = await assertPermission(ctx, "settings.manage");
    const current = (await getSettings(ctx)) as unknown as Record<string, unknown>;
    const prev: Record<string, unknown> = {};
    const next: Record<string, unknown> = {};
    for (const [k, val] of Object.entries(patch as Record<string, unknown>)) if (JSON.stringify(current[k]) !== JSON.stringify(val)) {
      prev[k] = current[k];
      next[k] = val;
    }
    if (!Object.keys(next).length) return null;
    if (typeof next.commissionAmount === "number" && next.commissionAmount < 0) throw new AppError("Commission cannot be negative", "VALIDATION");
    await saveSettings(ctx, next, group ?? "general");
    await audit(ctx, actorLite(user), { action: "SETTINGS_CHANGED", module: "settings", entityType: "settings", entityLabel: group ?? "general", previousValue: prev, newValue: next });
    return null;
  },
});

// ── Notification rules ───────────────────────────────────────
export const rules = query({
  args: {},
  returns: v.array(v.any()),
  handler: async (ctx) => {
    await assertPermission(ctx, "notifications.manage", "settings.view");
    const rows = await ctx.db.query("notificationRules").collect();
    return NOTIFICATION_EVENTS.map((e) => {
      const r = rows.find((x) => x.eventType === e.type);
      return { ...e, id: r?._id ?? null, enabled: r?.enabled ?? true, inApp: r?.inApp ?? true, email: r?.email ?? false, push: r?.push ?? false, priority: r?.priority ?? e.defaultPriority, recipientRoles: r?.recipientRoles ?? (e.defaultRole ? [e.defaultRole] : []), recipientUserIds: r?.recipientUserIds ?? [], notifyAssigned: r?.notifyAssigned ?? e.notifyAssigned, notifyActor: r?.notifyActor ?? false };
    });
  },
});

export const updateRule = mutation({
  args: { eventType: v.string(), patch: v.object({ enabled: v.optional(v.boolean()), inApp: v.optional(v.boolean()), email: v.optional(v.boolean()), push: v.optional(v.boolean()), priority: v.optional(v.string()), recipientRoles: v.optional(v.array(v.string())), recipientUserIds: v.optional(v.array(v.id("users"))), notifyActor: v.optional(v.boolean()), notifyAssigned: v.optional(v.boolean()) }) },
  returns: v.null(),
  handler: async (ctx, { eventType, patch }) => {
    const user = await assertPermission(ctx, "notifications.manage");
    const def = NOTIFICATION_EVENTS.find((e) => e.type === eventType);
    if (!def) throw new AppError("Rule not found", "NOT_FOUND");
    const rule = await ctx.db.query("notificationRules").withIndex("by_event", (q) => q.eq("eventType", eventType)).unique();
    if (rule) await ctx.db.patch(rule._id, patch);
    else await ctx.db.insert("notificationRules", { eventType, label: def.label, enabled: true, inApp: true, email: false, push: false, priority: def.defaultPriority, recipientRoles: def.defaultRole ? [def.defaultRole] : [], recipientUserIds: [], notifyAssigned: def.notifyAssigned, notifyActor: false, ...patch });
    await audit(ctx, actorLite(user), { action: "NOTIFICATION_RULE_CHANGED", module: "settings", entityType: "notificationRule", entityId: rule?._id, entityLabel: def.label, previousValue: rule ? { enabled: rule.enabled, inApp: rule.inApp, priority: rule.priority, recipientRoles: rule.recipientRoles } : null, newValue: patch });
    return null;
  },
});

// ── Notifications (per user, reactive) ───────────────────────
export const myNotifications = query({
  args: { limit: v.optional(v.number()) },
  returns: v.object({ unread: v.number(), items: v.array(v.any()) }),
  handler: async (ctx, { limit }) => {
    const actor = await currentActor(ctx);
    if (!actor) return { unread: 0, items: [] };
    const items = await ctx.db.query("notifications").withIndex("by_user_at", (q) => q.eq("userId", actor.id)).order("desc").take(limit ?? 60);
    const unread = (await ctx.db.query("notifications").withIndex("by_user_read", (q) => q.eq("userId", actor.id).eq("readAt", undefined)).take(200)).length;
    return { unread, items: items.map((n) => ({ ...n, id: n._id })) };
  },
});

export const markRead = mutation({
  args: { ids: v.optional(v.array(v.id("notifications"))) },
  returns: v.null(),
  handler: async (ctx, { ids }) => {
    const actor = await requireActor(ctx);
    const rows = ids ? await Promise.all(ids.map((id) => ctx.db.get(id))) : await ctx.db.query("notifications").withIndex("by_user_read", (q) => q.eq("userId", actor.id).eq("readAt", undefined)).take(500);
    for (const n of rows) if (n && n.userId === actor.id && !n.readAt) await ctx.db.patch(n._id, { readAt: Date.now() });
    return null;
  },
});
