import { connectDB } from "@/lib/db";
import { SystemLog } from "@/models/SystemLog";
import { TeamActivity } from "@/models/TeamActivity";
import { OrderActivity } from "@/models/OrderActivity";
import { Notification } from "@/models/Notification";
import { QmSubmission } from "@/models/QmSubmission";
import { Todo } from "@/models/Todo";
import { Order } from "@/models/Order";
import { Client } from "@/models/Client";
import { LOG_CATEGORIES, type LogCategory, type LogRange, type LogRow } from "@/lib/logConstants";

export * from "@/lib/logConstants";

function rangeSince(range: LogRange): Date {
  const hours = range === "24h" ? 24 : range === "7d" ? 24 * 7 : range === "30d" ? 24 * 30 : 24 * 90;
  return new Date(Date.now() - hours * 60 * 60 * 1000);
}

// Pro Quelle gedeckelt — der Zeitfilter begrenzt schon, das hier fängt nur
// pathologische Fälle ab. "capped" in der UI markiert, wenn eine Quelle den
// Deckel erreicht (dann sind ältere Einträge im Zeitraum nicht dabei).
const PER_SOURCE_LIMIT = 500;

const oid = (v: unknown) => (v == null ? "" : String(v));
const iso = (v: unknown) => new Date(v as string).toISOString();

type TodoActivityAgg = {
  _id: unknown;
  title?: string;
  activity?: { actorId?: unknown; text?: string; type?: string; createdAt?: unknown };
};

export async function getLogFeed(opts: {
  category: LogCategory | "all";
  range: LogRange;
  page: number;
  pageSize: number;
}): Promise<{
  rows: LogRow[];
  total: number;
  page: number;
  pageCount: number;
  counts: Record<LogCategory, number>;
  capped: boolean;
}> {
  await connectDB();
  const since = rangeSince(opts.range);

  const [sysLogs, teamActs, orderActs, notifs, qms, todoActs] = await Promise.all([
    SystemLog.find({ createdAt: { $gte: since } }).sort({ createdAt: -1 }).limit(PER_SOURCE_LIMIT).lean(),
    TeamActivity.find({ createdAt: { $gte: since } }).sort({ createdAt: -1 }).limit(PER_SOURCE_LIMIT).lean(),
    OrderActivity.find({ createdAt: { $gte: since } }).sort({ createdAt: -1 }).limit(PER_SOURCE_LIMIT).lean(),
    Notification.find({ createdAt: { $gte: since } }).sort({ createdAt: -1 }).limit(PER_SOURCE_LIMIT).lean(),
    QmSubmission.find({ $or: [{ createdAt: { $gte: since } }, { reviewedAt: { $gte: since } }] })
      .sort({ createdAt: -1 })
      .limit(PER_SOURCE_LIMIT)
      .lean(),
    Todo.aggregate<TodoActivityAgg>([
      { $match: { "activity.createdAt": { $gte: since } } },
      { $unwind: "$activity" },
      { $match: { "activity.createdAt": { $gte: since } } },
      { $sort: { "activity.createdAt": -1 } },
      { $limit: PER_SOURCE_LIMIT },
      { $project: { _id: 1, title: 1, activity: 1 } },
    ]),
  ]);

  const capped = [sysLogs, teamActs, orderActs, notifs, qms, todoActs].some((a) => a.length === PER_SOURCE_LIMIT);

  // Namen für actorIds / recipientIds in einem Rutsch auflösen.
  const idSet = new Set<string>();
  for (const l of sysLogs) if (l.actorId) idSet.add(oid(l.actorId));
  for (const n of notifs) if (n.recipientId) idSet.add(oid(n.recipientId));
  for (const q of qms) {
    if (q.submittedBy) idSet.add(oid(q.submittedBy));
    if (q.reviewedBy) idSet.add(oid(q.reviewedBy));
  }
  for (const t of todoActs) if (t.activity?.actorId) idSet.add(oid(t.activity.actorId));

  const orderIdSet = new Set<string>();
  for (const a of orderActs) if (a.orderId) orderIdSet.add(oid(a.orderId));

  const [clients, orders] = await Promise.all([
    idSet.size ? Client.find({ _id: { $in: [...idSet] } }).select("name").lean() : Promise.resolve([]),
    orderIdSet.size ? Order.find({ _id: { $in: [...orderIdSet] } }).select("characterOrConcept").lean() : Promise.resolve([]),
  ]);
  const nameById = new Map<string, string>();
  for (const c of clients) nameById.set(oid(c._id), String(c.name || "Unbekannt"));
  const orderLabelById = new Map<string, string>();
  for (const o of orders) orderLabelById.set(oid(o._id), String(o.characterOrConcept || "Auftrag"));

  const rows: LogRow[] = [];

  for (const l of sysLogs) {
    const level: LogCategory = l.level === "warn" ? "warn" : l.level === "info" ? "info" : "error";
    const ctx = (l.context && typeof l.context === "object" ? l.context : {}) as Record<string, unknown>;
    const meta: Record<string, string> = {};
    for (const [k, v] of Object.entries(ctx)) if (v) meta[k] = String(v).slice(0, 400);
    if (l.digest) meta.digest = String(l.digest);
    if (l.statusCode != null) meta.status = String(l.statusCode);
    if (l.fingerprint) meta.fingerprint = String(l.fingerprint);
    meta.quelle = String(l.source || "");
    rows.push({
      id: "sys-" + oid(l._id),
      ts: iso(l.createdAt),
      category: level,
      actor: l.actorId ? nameById.get(oid(l.actorId)) || "Mitarbeiter" : "System",
      title: String(l.message || "Fehler"),
      detail: [String(l.method || ""), String(l.route || "")].filter(Boolean).join(" "),
      route: String(l.route || ""),
      stack: String(l.stack || ""),
      meta,
    });
  }

  for (const a of teamActs) {
    rows.push({
      id: "team-" + oid(a._id),
      ts: iso(a.createdAt),
      category: "team",
      actor: String(a.actorName || "Unbekannt"),
      title: String(a.action || ""),
      detail: String(a.targetLabel || ""),
      route: "",
      stack: "",
      meta: {},
    });
  }

  for (const a of orderActs) {
    rows.push({
      id: "order-" + oid(a._id),
      ts: iso(a.createdAt),
      category: "orders",
      actor: a.actor === "client" ? "Kunde" : "Team",
      title: String(a.message || ""),
      detail: orderLabelById.get(oid(a.orderId)) || "Auftrag",
      route: `/de/admin/orders/${oid(a.orderId)}`,
      stack: "",
      meta: { typ: String(a.type || "") },
    });
  }

  for (const t of todoActs) {
    const a = t.activity || {};
    rows.push({
      id: "todo-" + oid(t._id) + "-" + new Date(a.createdAt as string).getTime(),
      ts: iso(a.createdAt),
      category: "todos",
      actor: a.actorId ? nameById.get(oid(a.actorId)) || "Mitarbeiter" : "System",
      title: String(t.title || "ToDo"),
      detail: String(a.text || a.type || ""),
      route: `/de/admin/todos#${oid(t._id)}`,
      stack: "",
      meta: { typ: String(a.type || "") },
    });
  }

  for (const n of notifs) {
    rows.push({
      id: "notif-" + oid(n._id),
      ts: iso(n.createdAt),
      category: "notifications",
      actor: "an " + (nameById.get(oid(n.recipientId)) || "Empfänger"),
      title: String(n.title || ""),
      detail: String(n.body || ""),
      route: String(n.link || ""),
      stack: "",
      meta: { typ: String(n.type || ""), gelesen: n.read ? "ja" : "nein" },
    });
  }

  for (const q of qms) {
    const who = q.anonymous ? "anonym" : nameById.get(oid(q.submittedBy)) || "Mitarbeiter";
    const kind = q.target === "hr" ? "Personal-Anliegen" : "QM-Meldung";
    const created = new Date(q.createdAt as unknown as string);
    if (created >= since) {
      rows.push({
        id: "qm-" + oid(q._id) + "-in",
        ts: created.toISOString(),
        category: "qm",
        actor: who,
        title: `${kind} eingereicht: ${String(q.title || "")}`,
        detail: String(q.description || "").slice(0, 300),
        route: "/de/admin/qm",
        stack: "",
        meta: { ziel: String(q.target || "qm"), status: String(q.status || "pending") },
      });
    }
    if (q.reviewedAt && new Date(q.reviewedAt) >= since) {
      rows.push({
        id: "qm-" + oid(q._id) + "-rev",
        ts: new Date(q.reviewedAt).toISOString(),
        category: "qm",
        actor: q.reviewedBy ? nameById.get(oid(q.reviewedBy)) || "Prüfer" : "Prüfer",
        title: `${kind} geprüft → ${String(q.status || "")}`,
        detail: String(q.reviewNote || "").slice(0, 300),
        route: "/de/admin/qm",
        stack: "",
        meta: { ziel: String(q.target || "qm") },
      });
    }
  }

  rows.sort((a, b) => (a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0));

  const counts = Object.fromEntries(LOG_CATEGORIES.map((c) => [c, 0])) as Record<LogCategory, number>;
  for (const r of rows) counts[r.category] += 1;

  const filtered = opts.category === "all" ? rows : rows.filter((r) => r.category === opts.category);
  const total = filtered.length;
  const pageSize = opts.pageSize;
  const pageCount = Math.max(1, Math.ceil(total / pageSize));
  const page = Math.min(Math.max(1, opts.page), pageCount);
  const paged = filtered.slice((page - 1) * pageSize, page * pageSize);

  return { rows: paged, total, page, pageCount, counts, capped };
}
