import { Router, type IRouter, type Request, type Response } from "express";
import { and, desc, eq, gte, inArray, lt, or, sql, type SQL } from "drizzle-orm";
import { db } from "@workspace/db";
import { auditLogsTable, usersTable } from "@workspace/db";
import { requireAdmin } from "../middlewares/auth";

const router: IRouter = Router();

const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
const EXPORT_ROW_CAP = 50_000;

// Actions that explicitly mark a forced deletion. Both the dedicated
// `*.force_delete` action names and the legacy `*.delete` rows whose metadata
// carries `force: true` count as forced deletions for the audit-log filter.
const FORCE_DELETE_ACTIONS = [
  "group.force_delete",
  "user.force_delete",
  "app.force_delete",
  "service.force_delete",
] as const;

const SOFT_DELETE_ACTIONS_WITH_FORCE_FLAG = [
  "group.delete",
  "user.delete",
  "app.delete",
] as const;

function parseUtcDate(s: string): Date | null {
  if (!DATE_RE.test(s)) return null;
  const d = new Date(`${s}T00:00:00.000Z`);
  return Number.isNaN(d.getTime()) ? null : d;
}

type AuditFilters = {
  action: string;
  forcedOnly: boolean;
  fromDate: Date | null;
  toDateExclusive: Date | null;
  targetType: string;
  targetId: number | null;
};

function parseFilters(req: Request, res: Response): AuditFilters | null {
  const action = typeof req.query.action === "string" ? req.query.action.trim() : "";
  const forcedOnlyRaw =
    typeof req.query.forcedOnly === "string" ? req.query.forcedOnly.trim() : "";
  const forcedOnly = forcedOnlyRaw === "true" || forcedOnlyRaw === "1";
  const fromStr = typeof req.query.from === "string" ? req.query.from.trim() : "";
  const toStr = typeof req.query.to === "string" ? req.query.to.trim() : "";
  const targetType =
    typeof req.query.targetType === "string" ? req.query.targetType.trim() : "";
  const targetIdStr =
    typeof req.query.targetId === "string" ? req.query.targetId.trim() : "";

  let fromDate: Date | null = null;
  let toDateExclusive: Date | null = null;
  if (fromStr) {
    fromDate = parseUtcDate(fromStr);
    if (!fromDate) {
      res.status(400).json({ error: "Invalid 'from' date (expected YYYY-MM-DD)" });
      return null;
    }
  }
  if (toStr) {
    const toDate = parseUtcDate(toStr);
    if (!toDate) {
      res.status(400).json({ error: "Invalid 'to' date (expected YYYY-MM-DD)" });
      return null;
    }
    toDateExclusive = new Date(toDate.getTime() + 24 * 60 * 60 * 1000);
  }
  if (fromDate && toDateExclusive && fromDate >= toDateExclusive) {
    res.status(400).json({ error: "'from' must be on or before 'to'" });
    return null;
  }
  let targetId: number | null = null;
  if (targetIdStr) {
    const parsed = Number(targetIdStr);
    if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 1) {
      res.status(400).json({ error: "Invalid 'targetId' (expected positive integer)" });
      return null;
    }
    targetId = parsed;
  }
  return { action, forcedOnly, fromDate, toDateExclusive, targetType, targetId };
}

function buildWhere(filters: AuditFilters): SQL | undefined {
  const conditions: SQL[] = [];
  if (filters.forcedOnly) {
    // Either an explicit *.force_delete action, or a legacy *.delete row whose
    // metadata records that the deletion was forced (force: true).
    const forcedClause = or(
      inArray(auditLogsTable.action, [...FORCE_DELETE_ACTIONS]),
      and(
        inArray(auditLogsTable.action, [...SOFT_DELETE_ACTIONS_WITH_FORCE_FLAG]),
        sql`${auditLogsTable.metadata} @> '{"force": true}'::jsonb`,
      ),
    );
    if (forcedClause) conditions.push(forcedClause);
  } else if (filters.action) {
    conditions.push(eq(auditLogsTable.action, filters.action));
  }
  if (filters.fromDate) conditions.push(gte(auditLogsTable.createdAt, filters.fromDate));
  if (filters.toDateExclusive)
    conditions.push(lt(auditLogsTable.createdAt, filters.toDateExclusive));
  if (filters.targetType)
    conditions.push(eq(auditLogsTable.targetType, filters.targetType));
  if (filters.targetId != null)
    conditions.push(eq(auditLogsTable.targetId, filters.targetId));
  return conditions.length > 0 ? and(...conditions) : undefined;
}

function csvEscape(value: unknown): string {
  if (value === null || value === undefined) return "";
  let s = typeof value === "string" ? value : String(value);
  // Mitigate CSV/spreadsheet formula injection (Excel, LibreOffice, Google
  // Sheets evaluate cells starting with =, +, -, @, or a tab/CR as formulas).
  // Prefix a single quote so the spreadsheet renders the value as text.
  if (s.length > 0 && /^[=+\-@\t\r]/.test(s)) {
    s = `'${s}`;
  }
  if (/[",\r\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
  return s;
}

router.get("/admin/audit-logs", requireAdmin, async (req, res): Promise<void> => {
  const filters = parseFilters(req, res);
  if (!filters) return;

  const limitRaw = Number(req.query.limit);
  const offsetRaw = Number(req.query.offset);
  const limit =
    Number.isFinite(limitRaw) && limitRaw > 0
      ? Math.min(Math.floor(limitRaw), 200)
      : 50;
  const offset =
    Number.isFinite(offsetRaw) && offsetRaw >= 0 ? Math.floor(offsetRaw) : 0;

  const whereClause = buildWhere(filters);

  const baseQuery = db
    .select({
      id: auditLogsTable.id,
      action: auditLogsTable.action,
      targetType: auditLogsTable.targetType,
      targetId: auditLogsTable.targetId,
      metadata: auditLogsTable.metadata,
      createdAt: auditLogsTable.createdAt,
      actorUserId: auditLogsTable.actorUserId,
      actorUsername: usersTable.username,
      actorDisplayNameAr: usersTable.displayNameAr,
      actorDisplayNameEn: usersTable.displayNameEn,
      actorAvatarUrl: usersTable.avatarUrl,
    })
    .from(auditLogsTable)
    .leftJoin(usersTable, eq(usersTable.id, auditLogsTable.actorUserId));

  const rows = await (whereClause ? baseQuery.where(whereClause) : baseQuery)
    .orderBy(desc(auditLogsTable.createdAt), desc(auditLogsTable.id))
    .limit(limit)
    .offset(offset);

  const countQuery = db
    .select({ count: sql<number>`count(*)::int` })
    .from(auditLogsTable);
  const [{ count: totalCount } = { count: 0 }] = await (whereClause
    ? countQuery.where(whereClause)
    : countQuery);

  const actionRows = await db
    .selectDistinct({ action: auditLogsTable.action })
    .from(auditLogsTable)
    .orderBy(auditLogsTable.action);

  const entries = rows.map((r) => ({
    id: r.id,
    action: r.action,
    targetType: r.targetType,
    targetId: r.targetId,
    metadata: r.metadata,
    createdAt: r.createdAt,
    actor:
      r.actorUserId != null && r.actorUsername != null
        ? {
            id: r.actorUserId,
            username: r.actorUsername,
            displayNameAr: r.actorDisplayNameAr,
            displayNameEn: r.actorDisplayNameEn,
            avatarUrl: r.actorAvatarUrl,
          }
        : null,
  }));

  const nextOffset = offset + rows.length < totalCount ? offset + rows.length : null;

  res.json({
    entries,
    totalCount,
    limit,
    offset,
    nextOffset,
    actions: actionRows.map((a) => a.action),
  });
});

router.get(
  "/admin/audit-logs/export",
  requireAdmin,
  async (req, res): Promise<void> => {
    const filters = parseFilters(req, res);
    if (!filters) return;

    const whereClause = buildWhere(filters);

    const baseQuery = db
      .select({
        action: auditLogsTable.action,
        targetType: auditLogsTable.targetType,
        targetId: auditLogsTable.targetId,
        metadata: auditLogsTable.metadata,
        createdAt: auditLogsTable.createdAt,
        actorUsername: usersTable.username,
      })
      .from(auditLogsTable)
      .leftJoin(usersTable, eq(usersTable.id, auditLogsTable.actorUserId));

    const rows = await (whereClause ? baseQuery.where(whereClause) : baseQuery)
      .orderBy(desc(auditLogsTable.createdAt), desc(auditLogsTable.id))
      .limit(EXPORT_ROW_CAP);

    const stamp = new Date().toISOString().slice(0, 10);
    const filename = `audit-log-${stamp}.csv`;
    res.setHeader("Content-Type", "text/csv; charset=utf-8");
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="${filename}"`,
    );
    res.setHeader("Cache-Control", "no-store");

    // UTF-8 BOM so spreadsheet apps (Excel) detect encoding correctly,
    // important for Arabic content stored in metadata.
    res.write("\uFEFF");
    res.write("action,actor_username,target_type,target_id,created_at,metadata\r\n");

    for (const r of rows) {
      const createdAtIso =
        r.createdAt instanceof Date
          ? r.createdAt.toISOString()
          : new Date(r.createdAt as unknown as string).toISOString();
      const metadataJson =
        r.metadata == null ? "" : JSON.stringify(r.metadata);
      const line = [
        csvEscape(r.action),
        csvEscape(r.actorUsername),
        csvEscape(r.targetType),
        csvEscape(r.targetId),
        csvEscape(createdAtIso),
        csvEscape(metadataJson),
      ].join(",");
      res.write(line);
      res.write("\r\n");
    }

    res.end();
  },
);

export default router;
