import { NextResponse } from 'next/server';
import { z } from 'zod';
import { inArray } from 'drizzle-orm';

import { withAuth, withPermission } from '@/lib/api/helpers';
import { parseQuery } from '@/lib/api/route-helpers';
import { searchAuditLogs } from '@/lib/services/audit-search.service';
import { db } from '@/lib/db';
import { user } from '@/lib/db/schema/users';
import { errorResponse } from '@/lib/errors';
import { createAuditLog } from '@/lib/audit';
import { redis } from '@/lib/redis';

const auditQuerySchema = z.object({
  limit: z.coerce.number().int().min(1).max(200).default(50),
  entityType: z.string().optional(),
  action: z.string().optional(),
  userId: z.string().optional(),
  entityId: z.string().optional(),
  severity: z.enum(['info', 'warning', 'error', 'critical']).optional(),
  source: z.enum(['user', 'system', 'auth', 'webhook', 'cron', 'job']).optional(),
  dateFrom: z.string().optional(),
  dateTo: z.string().optional(),
  /** Free-text query against the tsvector `search_text` column. */
  search: z.string().optional(),
  /** Cursor pair from the previous page's response. */
  cursorAt: z.string().optional(),
  cursorId: z.string().optional(),
});

export const GET = withAuth(
  withPermission('admin', 'view_audit_log', async (req, ctx) => {
    try {
      const query = parseQuery(req, auditQuerySchema);
      const cursor =
        query.cursorAt && query.cursorId
          ? { createdAt: new Date(query.cursorAt), id: query.cursorId }
          : undefined;
      const { rows, nextCursor } = await searchAuditLogs({
        portId: ctx.portId,
        q: query.search,
        userId: query.userId,
        action: query.action,
        entityType: query.entityType,
        entityId: query.entityId,
        severity: query.severity,
        source: query.source,
        from: query.dateFrom ? new Date(query.dateFrom) : undefined,
        to: query.dateTo ? new Date(query.dateTo) : undefined,
        cursor,
        limit: query.limit,
      });

      // Resolve actor emails in one batched query so the table can show
      // who did what without N+1 round trips.
      const userIds = Array.from(
        new Set(rows.map((r) => r.userId).filter((id): id is string => Boolean(id))),
      );
      const userRows = userIds.length
        ? await db
            .select({ id: user.id, email: user.email, name: user.name })
            .from(user)
            .where(inArray(user.id, userIds))
        : [];
      const userMap = new Map(userRows.map((u) => [u.id, u]));

      const data = rows.map((r) => ({
        ...r,
        actor: r.userId ? (userMap.get(r.userId) ?? null) : null,
      }));

      // Watch-the-watchers: record that an operator opened the audit log
      // page. Per-user 60s TTL dedupe so heavy filter-tweaking doesn't
      // bury the log in a flood of self-references; first request in
      // each window writes the row, subsequent requests within the
      // window are silent.
      if (!cursor) {
        const dedupeKey = `audit-view:${ctx.userId}:${ctx.portId}`;
        // SET NX returns 'OK' on insert, null when the key already exists
        // (TTL still ticking down).
        const inserted = await redis.set(dedupeKey, '1', 'EX', 60, 'NX');
        if (inserted === 'OK') {
          void createAuditLog({
            userId: ctx.userId,
            portId: ctx.portId,
            action: 'view',
            entityType: 'audit_log',
            entityId: 'list',
            metadata: {
              filters: {
                entityType: query.entityType,
                action: query.action,
                severity: query.severity,
                source: query.source,
                userId: query.userId,
                entityId: query.entityId,
                search: query.search,
              },
            },
            ipAddress: ctx.ipAddress,
            userAgent: ctx.userAgent,
            severity: 'warning',
          });
        }
      }

      return NextResponse.json({
        data,
        pagination: {
          nextCursor: nextCursor
            ? { createdAt: nextCursor.createdAt.toISOString(), id: nextCursor.id }
            : null,
        },
      });
    } catch (error) {
      return errorResponse(error);
    }
  }),
);
